skywatch 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in skywatch.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jeff Lindsay
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,175 @@
1
+ # Skywatch (alpha)
2
+
3
+ Simple alerting system that lets you define checks and alerts in any
4
+ language that are then magically run on Heroku.
5
+
6
+ NoOps! Polyglot! Free monitoring of anything!
7
+
8
+ ## Installation
9
+
10
+ $ gem install skywatch
11
+
12
+ ## Usage
13
+
14
+ It's a fairly powerful tool. Run help to see a list of subcommands.
15
+
16
+ $ skywatch -h
17
+
18
+ ## What the hell is this amazing thing??
19
+
20
+ Skywatch is a command-line utility that manages checking and alerting
21
+ scripts used by a builtin watcher service. Skywatch deploys these
22
+ scripts and service for you on Heroku where they can run and monitor
23
+ anything from the cloud for free.
24
+
25
+ The watcher service runs check scripts that can assert
26
+ anything at any frequency. If a check script returns a non-zero exit status,
27
+ it will fire any enabled alert scripts, passing it the output of the
28
+ check script and other useful data. Alert scripts can then act on this
29
+ assertion failure, such as send email, SMS, or webhook.
30
+
31
+ The check script will continue to run and potentially fail, but the
32
+ alert script only runs once, if without error. Only until a reset signal
33
+ is sent will it be ready to fire the alert again for any failed check
34
+ script. In this way, alerts work like [clip
35
+ indicators](http://help.adobe.com/en_US/audition/cs/using/WS58a04a822e3e5010548241038980c2c5-7f93.html)
36
+ in the audio recording world. They turn on once any clipping happens and
37
+ remain on until you manually reset them.
38
+
39
+ You manage your scripts locally with the skywatch command, or by hand
40
+ since they're just files in directories. When you want to deploy script
41
+ changes, toggle enabled scripts, or reset the alert state, you can run a
42
+ skywatch command and it will handle pushing changes to Heroku for you.
43
+
44
+ ## Getting Started
45
+
46
+ The skywatch command manages a directory containing check scripts and
47
+ alert scripts. You can make a new directory and let skywatch set this up
48
+ for you:
49
+
50
+ $ mkdir my-watcher
51
+ $ cd my-watcher
52
+ $ skywatch init
53
+
54
+ At this point, it will have you authenticate with your Heroku
55
+ credentials if you haven't already. Grab a free account if you don't
56
+ have one.
57
+
58
+ When you run `skywatch init` authenticated it will create some example
59
+ alerts and checks for you, and deploy a Heroku app. None of the checks
60
+ or alerts are enabled by default. See everything by just running
61
+ `skywatch` from the directory:
62
+
63
+ $ skywatch
64
+ Checks for fathomless-crag-3169
65
+ example every 30s disabled
66
+ skywatch_watchers every 3600s disabled
67
+ Alerts for fathomless-crag-3169
68
+ email disabled
69
+
70
+ Take a look at all the files in the directory. Checks and alerts are nothing
71
+ more than scripts. Checks have a naming convention of `<interval>.<name>`,
72
+ and enabling and disabling is just setting the execute bit on the
73
+ scripts. There's nothing the `skywatch` command does that you can't
74
+ easily do by hand. It just happens to be terribly convenient.
75
+
76
+ $ skywatch edit alert email
77
+
78
+ This will open your editor and you can see the example email alert
79
+ script is using SendGrid. In fact, when you ran `skywatch init`, you
80
+ were set up with a free SendGrid starter addon for 600 emails a day. So
81
+ let's try it by putting your email address in the `TO` variable of the
82
+ script. Now enable the alert:
83
+
84
+ $ skywatch enable alert email
85
+
86
+ Let's create a new check script in bash that fails so we can get the
87
+ alert.
88
+
89
+ $ skywatch create check failure_test 30
90
+
91
+ The last argument is the interval. Intervals are always in seconds. All
92
+ this did was create a new file under the `checks` directory with a
93
+ little bit of boiler plate. Let's replace its contents with this:
94
+
95
+ #!/usr/bin/env bash
96
+ echo "Oh no, a failed check."
97
+ exit 255
98
+
99
+ Enable the check and then we'll deploy:
100
+
101
+ $ skywatch enable check failure_test
102
+ $ skywatch deploy
103
+
104
+ It's going to move some files around and then deploy to Heroku. It keeps
105
+ a staging directory called `.skywatch`, which is a Git repo used to push
106
+ to Heroku. It automatically adds this to a `.gitignore` file, so you can
107
+ version your scripts with Git and not worry about this implementation
108
+ detail.
109
+
110
+ Once it's finished, you might want to run monitor to see how it went and
111
+ what's going on. This is just tailing the Heroku logs of the watcher
112
+ service:
113
+
114
+ $ skywatch monitor
115
+
116
+ You can run this whenever to see what it's doing. You'll probably see
117
+ that it triggered the alert. Go check your email! That will be the only
118
+ email you get, regardless of whether the check starts to work again and
119
+ then fail again. No flapping. You have to manually reset:
120
+
121
+ $ skywatch reset
122
+
123
+ This should cause another alert email within 30 seconds. And of course,
124
+ you can tear everything down with destroy:
125
+
126
+ $ skywatch destroy
127
+
128
+ This destroys the Heroku app and the `.skywatch` directory. It doesn't
129
+ touch your scripts at all. In fact, you can run `skywatch init` again if
130
+ you'd like.
131
+
132
+ The source code to all this is terribly simple. The watcher service is
133
+ only about 50 lines of Ruby. Everything else is just file operations.
134
+ In fact, the little state it maintains is kept in file metadata. For how
135
+ automated it is, skywatch has to be one of the simplest monitoring services
136
+ ever.
137
+
138
+ ## Writing Check Scripts
139
+
140
+ Check scripts are any executable script using the shebang to define the
141
+ interpreter. Heroku has most common languages built-in to its Cedar
142
+ stack, so feel free to use Python, Perl, Ruby, whatever. I like bash.
143
+
144
+ The only conventions of check scripts are the interval-in-the-filename and that a non-zero exit status will fire the alerts. Any output of the check script will be piped into STDIN of the alert script, so try be verbose but not too
145
+ verbose.
146
+
147
+ If you're using bash, it's a good idea to use `set -e` so any failed
148
+ subcommand will bubble up.
149
+
150
+ ## Writing Alert Scripts
151
+
152
+ Like check scripts, alert scripts can be written in any language. Also,
153
+ like check scripts, the exit status is important. If an alert script
154
+ exit status is non-zero, it will run again with the next failure of the check
155
+ script.
156
+
157
+ The alert script is given the output of the check script via STDIN. It's
158
+ also given 2 arguments. The first is the name of the check script. The
159
+ second is the exit status of the failed check script.
160
+
161
+ The output of an alert script is ignored. It might be a good idea to log
162
+ the output of failed alert scripts. You'd then be able to see it via
163
+ `skywatch monitor`. Sounds like a contribution idea.
164
+
165
+ ## Contributing
166
+
167
+ 1. Fork it
168
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
169
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
170
+ 4. Push to the branch (`git push origin my-new-feature`)
171
+ 5. Create new Pull Request
172
+
173
+ ## License
174
+
175
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :test do
4
+ exec "bundle exec rspec spec"
5
+ end
data/bin/skywatch ADDED
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'commander/import'
4
+ require 'highline/import'
5
+
6
+ require 'skywatch'
7
+
8
+ include Skywatch
9
+
10
+ program :name, 'skywatch'
11
+ program :version, Skywatch::VERSION
12
+ program :description, 'Simple, Unix-oriented alerting system'
13
+ default_command :all
14
+
15
+ def display_check(check)
16
+ status = check.enabled? ? "'enabled', :green" : "'disabled', :red"
17
+ say " %-24s %-16s %s" % [
18
+ check.name, "every #{check.interval}s", "<%= color #{status} %>"]
19
+ end
20
+
21
+ def display_alert(alert)
22
+ status = alert.enabled? ? "'enabled', :green" : "'disabled', :red"
23
+ say " %-16s %s" % [
24
+ alert.name, "<%= color #{status} %>"]
25
+ end
26
+
27
+ def ensure_logged_in
28
+ if not logged_in?
29
+ say_warning "You're not logged into Heroku. Please login then try again"
30
+ login
31
+ end
32
+ end
33
+
34
+ command :init do |c|
35
+ c.syntax = 'skywatch init [name]'
36
+ c.description = 'Initializes skywatch in the current directory'
37
+ c.action do |args, options|
38
+ ensure_logged_in
39
+ init args.first
40
+ say_ok "Skywatch initialized as #{name}"
41
+ end
42
+ end
43
+
44
+ command :checks do |c|
45
+ c.syntax = 'skywatch checks'
46
+ c.description = 'Lists checks'
47
+ c.action do |args, options|
48
+ puts " Checks for #{name}"
49
+ checks.each do |check|
50
+ display_check check
51
+ end
52
+ end
53
+ end
54
+
55
+ command :alerts do |c|
56
+ c.syntax = 'skywatch alerts'
57
+ c.description = 'Lists alerts'
58
+ c.action do |args, options|
59
+ puts " Alerts for #{name}"
60
+ alerts.each do |alert|
61
+ display_alert alert
62
+ end
63
+ end
64
+ end
65
+
66
+ command :all do |c|
67
+ c.syntax = 'skywatch all'
68
+ c.description = 'Lists checks and alerts'
69
+ c.action do |args, options|
70
+ puts " Checks for #{name}"
71
+ checks.each do |check|
72
+ display_check check
73
+ end
74
+ puts " Alerts for #{name}"
75
+ alerts.each do |alert|
76
+ display_alert alert
77
+ end
78
+ end
79
+ end
80
+
81
+ command :'enable check' do |c|
82
+ c.syntax = 'skywatch enable check <name>'
83
+ c.description = 'Enable a check'
84
+ c.action do |args, options|
85
+ fail unless args.length > 0
86
+ check = check(args.first)
87
+ check.enable
88
+ display_check check
89
+ end
90
+ end
91
+
92
+ command :'disable check' do |c|
93
+ c.syntax = 'skywatch disable check <name>'
94
+ c.description = 'Disable a check'
95
+ c.action do |args, options|
96
+ fail unless args.length > 0
97
+ check = check(args.first)
98
+ check.disable
99
+ display_check check
100
+ end
101
+ end
102
+
103
+ command :'enable alert' do |c|
104
+ c.syntax = 'skywatch enable alert <name>'
105
+ c.description = 'Enable an alert'
106
+ c.action do |args, options|
107
+ fail unless args.length > 0
108
+ alert = alert(args.first)
109
+ alert.enable
110
+ display_alert alert
111
+ end
112
+ end
113
+
114
+ command :'disable alert' do |c|
115
+ c.syntax = 'skywatch disable alert <name>'
116
+ c.description = 'Disable an alert'
117
+ c.action do |args, options|
118
+ fail unless args.length > 0
119
+ alert = alert(args.first)
120
+ alert.disable
121
+ display_alert alert
122
+ end
123
+ end
124
+
125
+ command :'create alert' do |c|
126
+ c.syntax = 'skywatch create alert <name> [interpreter]'
127
+ c.description = 'Create an alert script. Default interpreter is bash'
128
+ c.action do |args, options|
129
+ fail unless args.length > 0
130
+ interpreter = args.length > 1 ? args[1] : 'bash'
131
+ alert = create_alert(args.first, interpreter)
132
+ display_alert alert
133
+ end
134
+ end
135
+
136
+ command :'create check' do |c|
137
+ c.syntax = 'skywatch create check <name> <interval> [interpreter]'
138
+ c.description = 'Create a check script. Default interpreter is bash'
139
+ c.action do |args, options|
140
+ fail unless args.length > 1
141
+ interpreter = args.length > 2 ? args[2] : 'bash'
142
+ check = create_check(args[0], args[1], interpreter)
143
+ display_check check
144
+ end
145
+ end
146
+
147
+ command :'edit alert' do |c|
148
+ c.syntax = 'skywatch edit alert <name>'
149
+ c.description = 'Open the alert script in an editor'
150
+ c.action do |args|
151
+ fail unless args.length > 0
152
+ if ENV['EDITOR'].to_s.empty?
153
+ puts "The EDITOR environment variable is not defined."
154
+ else
155
+ alert = alert(args.first)
156
+ exec "#{ENV['EDITOR']} #{alert.path}"
157
+ end
158
+ end
159
+ end
160
+
161
+ command :'edit check' do |c|
162
+ c.syntax = 'skywatch edit check <name>'
163
+ c.description = 'Open the check script in an editor'
164
+ c.action do |args|
165
+ fail unless args.length > 0
166
+ if ENV['EDITOR'].to_s.empty?
167
+ puts "The EDITOR environment variable is not defined."
168
+ else
169
+ check = check(args.first)
170
+ exec "#{ENV['EDITOR']} #{check.path}"
171
+ end
172
+ end
173
+ end
174
+
175
+ command :reset do |c|
176
+ c.syntax = 'skywatch reset'
177
+ c.description = 'Reset running check states'
178
+ c.action do
179
+ ensure_logged_in
180
+ reset
181
+ end
182
+ end
183
+
184
+ command :monitor do |c|
185
+ c.syntax = 'skywatch monitor'
186
+ c.description = 'Monitor the running checks'
187
+ c.action do
188
+ ensure_logged_in
189
+ monitor
190
+ end
191
+ end
192
+
193
+ command :deploy do |c|
194
+ c.syntax = 'skywatch deploy'
195
+ c.description = 'Deploy this skywatch'
196
+ c.action do
197
+ ensure_logged_in
198
+ deploy
199
+ end
200
+ end
201
+
202
+ command :destroy do |c|
203
+ c.syntax = 'skywatch destroy'
204
+ c.description = 'Destroy skywatch Heroku app and stage directory'
205
+ c.action do
206
+ ensure_logged_in
207
+ destroy
208
+ end
209
+ end
210
+
211
+ #command :'destroy alert' do |c|
212
+ # c.syntax = 'skywatch destroy alert <name>'
213
+ # c.description = 'Delete an alert script'
214
+ # c.action do |args, options|
215
+ # fail unless args.length > 0
216
+ # alert = alert(args.first)
217
+ # alert.destroy
218
+ # say " Alert <%= color '#{alert.name}', :bold %> has been destroyed"
219
+ # end
220
+ #end
221
+
222
+ #command :'destroy check' do |c|
223
+ # c.syntax = 'skywatch destroy check <name>'
224
+ # c.description = 'Delete a check script'
225
+ # c.action do |args, options|
226
+ # fail unless args.length > 0
227
+ # check = check(args.first)
228
+ # check.destroy
229
+ # say " Check <%= color '#{check.name}', :bold %> has been destroyed"
230
+ # end
231
+ #end
data/lib/skywatch.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "skywatch/version"
2
+ require "skywatch/tool"
3
+
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ TO=
3
+ SUBJECT="[skywatch] $1"
4
+ BODY=`echo -e "Failure with status $2:\n\n$(cat)"`
5
+ set -e
6
+ curl \
7
+ -X 'POST' \
8
+ -F "api_user=$SENDGRID_USERNAME" \
9
+ -F "api_key=$SENDGRID_PASSWORD" \
10
+ -F "to=$TO" \
11
+ -F "subject=$SUBJECT" \
12
+ -F "text=$BODY" \
13
+ -F "from=$TO" \
14
+ --silent --fail "https://sendgrid.com/api/mail.send.json"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+ curl --trace-ascii --silent --fail http://example.com
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ require "net/https"
3
+ require "uri"
4
+ require "json"
5
+
6
+ uri = URI.parse("https://api.github.com/repos/progrium/skywatch")
7
+ http = Net::HTTP.new(uri.host, uri.port)
8
+ http.use_ssl = true
9
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
10
+
11
+ request = Net::HTTP::Get.new(uri.request_uri)
12
+
13
+ response = http.request(request)
14
+ repo = JSON.parse(response.body)
15
+
16
+ if repo['watchers_count'] > 1000
17
+ raise "#{repo['watchers_count']} watchers!"
18
+ end
@@ -0,0 +1,211 @@
1
+ require 'fileutils'
2
+
3
+ module Skywatch
4
+ include FileUtils
5
+
6
+ def init(name="")
7
+ fail if skywatch?
8
+ mkdir '.skywatch'
9
+ begin
10
+ cd '.skywatch' do
11
+ system "git init"
12
+ puts `heroku create #{name}`
13
+ fail if not $?.exitstatus.zero?
14
+ puts `heroku addons:add sendgrid:starter`
15
+ system "cp #{watcher_path}/* ."
16
+ system "bundle install > /dev/null 2>&1"
17
+ end
18
+ mkdir_p 'alerts'
19
+ if Dir['alerts/*'].empty?
20
+ system "cp #{builtin_path}/alerts/* alerts"
21
+ end
22
+ mkdir_p 'checks'
23
+ if Dir['checks/*'].empty?
24
+ system "cp #{builtin_path}/checks/* checks"
25
+ end
26
+ system "echo '.skywatch' >> .gitignore"
27
+ deploy
28
+ cd '.skywatch' do
29
+ system "heroku ps:scale watcher=1"
30
+ end
31
+ rescue
32
+ rm_rf '.skywatch'
33
+ end
34
+ end
35
+
36
+ def logged_in?
37
+ `heroku auth:token < /dev/null`
38
+ $?.exitstatus.zero?
39
+ end
40
+
41
+ def login
42
+ exec 'heroku auth:login'
43
+ end
44
+
45
+ def watcher_path
46
+ File.dirname(__FILE__)+"/watcher"
47
+ end
48
+
49
+ def builtin_path
50
+ File.dirname(__FILE__)+"/builtin"
51
+ end
52
+
53
+ def skywatch?
54
+ not Dir["./.skywatch"].empty?
55
+ end
56
+
57
+ def checks
58
+ fail unless skywatch?
59
+ Dir["./checks/*"].collect{|c| Check[c] }
60
+ end
61
+
62
+ def alerts
63
+ fail unless skywatch?
64
+ Dir["./alerts/*"].collect{|c| Alert[c] }
65
+ end
66
+
67
+ def check(name)
68
+ fail unless skywatch?
69
+ Check[Dir["./checks/*.#{name}"].first]
70
+ end
71
+
72
+ def alert(name)
73
+ fail unless skywatch?
74
+ Alert[Dir["./alerts/#{name}"].first]
75
+ end
76
+
77
+ def create_alert(name, interpreter)
78
+ fail unless skywatch?
79
+ fail if File.exist? "./alerts/#{name}"
80
+ File.open("./alerts/#{name}", 'w') do |f|
81
+ f.write "#!/usr/bin/env #{interpreter}\n"
82
+ if interpreter == 'bash'
83
+ f.write "set -e\n"
84
+ end
85
+ f.write "# Write your alert here"
86
+ end
87
+ alert(name)
88
+ end
89
+
90
+ def create_check(name, interval, interpreter)
91
+ fail unless skywatch?
92
+ fail if File.exist? "./checks/#{interval}.#{name}"
93
+ File.open("./checks/#{interval}.#{name}", 'w') do |f|
94
+ f.write "#!/usr/bin/env #{interpreter}\n"
95
+ if interpreter == 'bash'
96
+ f.write "set -e\n"
97
+ end
98
+ f.write "# Write your check here"
99
+ end
100
+ check(name)
101
+ end
102
+
103
+ def name
104
+ fail unless skywatch?
105
+ cd ".skywatch" do
106
+ if not File.exist? 'name'
107
+ name = `eval $(heroku apps:info -s | grep "^name=") && echo "$name"`.strip
108
+ File.open('name', 'w') {|f| f.write(name) }
109
+ end
110
+ @@name ||= File.read('name')
111
+ end
112
+ @@name
113
+ end
114
+
115
+ def reset
116
+ fail unless skywatch?
117
+ cd ".skywatch" do
118
+ puts `heroku restart`
119
+ end
120
+ end
121
+
122
+ def monitor
123
+ fail unless skywatch?
124
+ cd ".skywatch" do
125
+ exec "heroku logs -t -n 10"
126
+ end
127
+ end
128
+
129
+ def stage
130
+ fail unless skywatch?
131
+ cd '.skywatch' do
132
+ system 'git rm -f alerts/* > /dev/null 2>&1'
133
+ system 'git rm -f checks/* > /dev/null 2>&1'
134
+ end
135
+ mkdir_p '.skywatch/alerts'
136
+ mkdir_p '.skywatch/checks'
137
+ system 'cp alerts/* .skywatch/alerts'
138
+ system 'cp checks/* .skywatch/checks'
139
+ commit
140
+ end
141
+
142
+ def commit
143
+ fail unless skywatch?
144
+ cd '.skywatch' do
145
+ system 'git add .'
146
+ if not `git status`.include? "nothing to commit"
147
+ system 'git commit -m "skywatch commit"'
148
+ end
149
+ end
150
+ end
151
+
152
+ def deploy
153
+ stage
154
+ cd '.skywatch' do
155
+ puts `git push heroku master`
156
+ end
157
+ end
158
+
159
+ def destroy
160
+ fail unless skywatch?
161
+ puts `heroku apps:destroy -a #{name} --confirm #{name}`
162
+ rm_rf '.skywatch'
163
+ end
164
+
165
+ class Script
166
+ attr :path
167
+ attr :name
168
+
169
+ def self.[](path)
170
+ self.new(path)
171
+ end
172
+
173
+ def initialize(path)
174
+ @path = path
175
+ @name = File.basename(path)
176
+ end
177
+
178
+ def enabled?
179
+ File.executable? @path
180
+ end
181
+
182
+ def enable
183
+ chmod File.stat(@path).mode | 0700, @path
184
+ end
185
+
186
+ def disable
187
+ enable # since this is relative, force 0700
188
+ chmod File.stat(@path).mode & ~0100, @path
189
+ end
190
+
191
+ def destroy
192
+ rm @path
193
+ end
194
+ end
195
+
196
+ class Check < Script
197
+ attr :interval
198
+
199
+ def initialize(path)
200
+ interval, name = File.basename(path).split('.', 2)
201
+ @path = path
202
+ @name = name
203
+ @interval = interval.to_i
204
+ end
205
+ end
206
+
207
+ class Alert < Script
208
+ end
209
+
210
+ end
211
+
@@ -0,0 +1,3 @@
1
+ module Skywatch
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gem 'clockwork'
@@ -0,0 +1 @@
1
+ watcher: bundle exec clockwork watcher.rb
@@ -0,0 +1,56 @@
1
+ require 'clockwork'
2
+ require 'fileutils'
3
+
4
+ include Clockwork
5
+ include FileUtils
6
+
7
+ Clockwork.configure do |config|
8
+ config[:logger] = Logger.new(STDOUT)
9
+ config[:logger].level = Logger::ERROR
10
+ end
11
+
12
+ def mark_pass(check); chmod File.stat(check).mode & ~0003, check; end
13
+ def mark_fail(check); chmod File.stat(check).mode | 0007, check; end
14
+ def mark_alerted(check); chmod File.stat(check).mode | 0070, check; end
15
+ def marked_alerted?(check)
16
+ `stat -c %A #{check} | sed 's/......\\(.\\).\\+/\\1/'` == "x\n"
17
+ end
18
+ def executables(glob)
19
+ Dir[glob].select {|path| File.executable? path }
20
+ end
21
+
22
+ mkdir_p 'output'
23
+ mkdir_p 'checks'
24
+ mkdir_p 'alerts'
25
+
26
+ executables('checks/*').each do |check|
27
+ interval, name = File.basename(check).split('.', 2)
28
+
29
+ puts "loading check #{name} for every #{interval} seconds"
30
+ every interval.to_i.seconds, name do
31
+
32
+ puts "checking #{name} (#{File.stat(check).mode.to_s(8)})"
33
+ `#{check} > output/#{name} 2>&1`
34
+
35
+ status = $?.exitstatus
36
+ if status.zero?
37
+ mark_pass check
38
+ else
39
+ puts " #{name} check failed with status #{status}"
40
+ mark_fail check
41
+
42
+ if not marked_alerted? check
43
+ executables('alerts/*').each do |alert|
44
+ puts " sending #{File.basename(alert)} alert for #{name}"
45
+ `cat output/#{name} | #{alert} #{name} #{status} > /dev/null 2>&1`
46
+
47
+ if $?.exitstatus.zero?
48
+ mark_alerted check
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end # every
55
+
56
+ end
data/skywatch.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'skywatch/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "skywatch"
8
+ gem.version = Skywatch::VERSION
9
+ gem.authors = ["Jeff Lindsay"]
10
+ gem.email = ["progrium@gmail.com"]
11
+ gem.description = %q{Simple alerting system that lets you define checks and alerts in any language that are then magically run on Heroku.}
12
+ gem.summary = %q{Simple, polyglot alerting system}
13
+ gem.homepage = "http://github.com/progrium/skywatch"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency "rspec", "~> 2.6"
21
+
22
+ gem.add_dependency "commander", "~> 4.1"
23
+ gem.add_dependency "heroku", "~> 2.33"
24
+ gem.add_dependency "bundler", "~> 1.2"
25
+ end
@@ -0,0 +1,8 @@
1
+ require 'skywatch'
2
+
3
+ describe Skywatch do
4
+ it "runs" do
5
+ false.should eql(false)
6
+ end
7
+ end
8
+
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: skywatch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeff Lindsay
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.6'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.6'
30
+ - !ruby/object:Gem::Dependency
31
+ name: commander
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '4.1'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '4.1'
46
+ - !ruby/object:Gem::Dependency
47
+ name: heroku
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.33'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.33'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.2'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1.2'
78
+ description: Simple alerting system that lets you define checks and alerts in any
79
+ language that are then magically run on Heroku.
80
+ email:
81
+ - progrium@gmail.com
82
+ executables:
83
+ - skywatch
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - Gemfile
89
+ - LICENSE
90
+ - README.md
91
+ - Rakefile
92
+ - bin/skywatch
93
+ - lib/skywatch.rb
94
+ - lib/skywatch/builtin/alerts/email
95
+ - lib/skywatch/builtin/checks/30.example
96
+ - lib/skywatch/builtin/checks/3600.skywatch_watchers
97
+ - lib/skywatch/tool.rb
98
+ - lib/skywatch/version.rb
99
+ - lib/skywatch/watcher/Gemfile
100
+ - lib/skywatch/watcher/Procfile
101
+ - lib/skywatch/watcher/watcher.rb
102
+ - skywatch.gemspec
103
+ - spec/skywatch_spec.rb
104
+ homepage: http://github.com/progrium/skywatch
105
+ licenses: []
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 1.8.23
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Simple, polyglot alerting system
128
+ test_files:
129
+ - spec/skywatch_spec.rb