cronitor 1.1.4 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/publish.yml +29 -0
- data/.github/workflows/test.yml +54 -0
- data/.rubocop.yml +41 -0
- data/Gemfile +2 -0
- data/README.md +187 -61
- data/Rakefile +8 -0
- data/bin/console +1 -0
- data/cronitor.gemspec +18 -12
- data/lib/cronitor.rb +64 -84
- data/lib/cronitor/config.rb +33 -0
- data/lib/cronitor/error.rb +9 -1
- data/lib/cronitor/monitor.rb +193 -0
- data/lib/cronitor/version.rb +4 -2
- metadata +70 -37
- data/.travis.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0d5c6dc2547c52ed2ccee489b2ea5428c6badd902f996ea076cd196e6c4bb1c3
|
4
|
+
data.tar.gz: 57b37c91645625096fbe19004d181a6ed375517e02379b6d8365676c4d33839b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 349711cfbefe7cb0519e872862f9f54fd24d877850ac63714db59b62f4d7f7008bc39008fb74bca22eebb85c72d1e90e0fdf7c1a5fcb5ace205af63611440579
|
7
|
+
data.tar.gz: 10e8d151d5bd52f464aa92dcd737f1e29354699b66832f17f977b5608b4716532406ba21ac0d83e439732abad9558f46feb0fc0037e06b7ce79169622a36a283
|
@@ -0,0 +1,29 @@
|
|
1
|
+
---
|
2
|
+
name: Publish
|
3
|
+
|
4
|
+
on:
|
5
|
+
push:
|
6
|
+
tags:
|
7
|
+
- '*'
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
publish:
|
11
|
+
needs:
|
12
|
+
- lint
|
13
|
+
- test
|
14
|
+
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v2
|
19
|
+
- name: Set up Ruby 2.7
|
20
|
+
uses: actions/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: 2.7.x
|
23
|
+
- name: Build and test with Rake
|
24
|
+
run: |
|
25
|
+
gem install bundler
|
26
|
+
bundle install --jobs 4 --retry 3
|
27
|
+
bundle exec rake release
|
28
|
+
with:
|
29
|
+
api_key: ${{secrets.RUBYGEMS_API_KEY}}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
---
|
2
|
+
name: Test
|
3
|
+
|
4
|
+
on:
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- master
|
8
|
+
pull_request:
|
9
|
+
branches:
|
10
|
+
- master
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
lint:
|
14
|
+
strategy:
|
15
|
+
fail-fast: false
|
16
|
+
matrix:
|
17
|
+
ruby:
|
18
|
+
- '2.7'
|
19
|
+
- '3.0'
|
20
|
+
|
21
|
+
runs-on: ubuntu-latest
|
22
|
+
|
23
|
+
steps:
|
24
|
+
- uses: actions/checkout@v2
|
25
|
+
- name: Set up Ruby 2.7
|
26
|
+
uses: ruby/setup-ruby@v1
|
27
|
+
with:
|
28
|
+
ruby-version: ${{ matrix.ruby }}
|
29
|
+
bundler-cache: true
|
30
|
+
- name: Run Rubocop
|
31
|
+
run: bundle exec rake rubocop
|
32
|
+
|
33
|
+
spec:
|
34
|
+
strategy:
|
35
|
+
fail-fast: false
|
36
|
+
matrix:
|
37
|
+
ruby:
|
38
|
+
- '2.7'
|
39
|
+
- '3.0'
|
40
|
+
|
41
|
+
runs-on: ubuntu-latest
|
42
|
+
|
43
|
+
steps:
|
44
|
+
- uses: actions/checkout@v2
|
45
|
+
- name: Set up Ruby 2.7
|
46
|
+
uses: ruby/setup-ruby@v1
|
47
|
+
with:
|
48
|
+
ruby-version: ${{ matrix.ruby }}
|
49
|
+
bundler-cache: true
|
50
|
+
- name: Build and test with Rake
|
51
|
+
run: |
|
52
|
+
gem install bundler
|
53
|
+
bundle install --jobs 4 --retry 3
|
54
|
+
bundle exec rake
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
---
|
2
|
+
require:
|
3
|
+
- rubocop-rake
|
4
|
+
- rubocop-rspec
|
5
|
+
|
6
|
+
inherit_mode:
|
7
|
+
merge:
|
8
|
+
- Exclude
|
9
|
+
|
10
|
+
AllCops:
|
11
|
+
NewCops: enable
|
12
|
+
Exclude:
|
13
|
+
- 'spec/cronitor_spec.rb'
|
14
|
+
|
15
|
+
Metrics/AbcSize:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Metrics/ClassLength:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Metrics/BlockLength:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Metrics/CyclomaticComplexity:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Metrics/PerceivedComplexity:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Metrics/MethodLength:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Style/Documentation:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
Style/RaiseArgs:
|
37
|
+
EnforcedStyle: compact
|
38
|
+
|
39
|
+
Style/EachWithObject:
|
40
|
+
Enabled: false
|
41
|
+
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,104 +1,230 @@
|
|
1
|
-
# Cronitor
|
1
|
+
# Cronitor Ruby Library
|
2
2
|
|
3
|
-
|
3
|
+
![Test](https://github.com/cronitorio/cronitor-ruby/workflows/Test/badge.svg)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/cronitor.svg)](https://badge.fury.io/rb/cronitor)
|
5
5
|
|
6
|
-
[Cronitor](https://cronitor.io/) is a service for heartbeat-style monitoring of just about anything that can send an HTTP request.
|
7
6
|
|
8
|
-
|
7
|
+
[Cronitor](https://cronitor.io/) provides dead simple monitoring for cron jobs, daemons, data pipelines, queue workers, and anything else that can send or receive an HTTP request. The Cronitor Ruby library provides convenient access to the Cronitor API from applications written in Ruby.
|
8
|
+
|
9
|
+
## Documentation
|
10
|
+
See our [API docs](https://cronitor.io/docs/api) for a detailed reference information about the APIs this library uses for configuring monitors and sending telemetry pings.
|
9
11
|
|
10
12
|
## Installation
|
11
13
|
|
12
|
-
|
14
|
+
```
|
15
|
+
gem install cronitor
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
The package needs to be configured with your account's `API key`, which is available on the [account settings](https://cronitor.io/settings) page. You can also optionally specify an `API Version` (default: account default) and `Environment` (default: account default).
|
21
|
+
|
22
|
+
These can be supplied using the environment variables `CRONITOR_API_KEY`, `CRONITOR_API_VERSION`, `CRONITOR_ENVIRONMENT` or set directly on the cronitor object.
|
13
23
|
|
14
24
|
```ruby
|
15
|
-
|
25
|
+
require 'cronitor'
|
26
|
+
|
27
|
+
Cronitor.api_key = 'apiKey123'
|
28
|
+
Cronitor.api_version = '2020-10-01'
|
29
|
+
Cronitor.environment = 'staging'
|
16
30
|
```
|
17
31
|
|
18
|
-
|
32
|
+
You can also use a YAML config file to manage all of your monitors (_see Create and Update Monitors section below_). The path to this file can be supplied using the enviroment variable `CRONITOR_CONFIG` or call `cronitor.read_config()`.
|
19
33
|
|
20
|
-
|
34
|
+
```ruby
|
35
|
+
require 'cronitor'
|
21
36
|
|
22
|
-
|
37
|
+
Cronitor.read_config('./path/to/cronitor.yaml')
|
38
|
+
```
|
23
39
|
|
24
|
-
$ gem install cronitor
|
25
40
|
|
26
|
-
|
41
|
+
### Monitor Any Block
|
27
42
|
|
28
|
-
|
43
|
+
The quickest way to start using this library is to wrap a block of code with the `#job` helper. It will report the start time, end time, and exit state to Cronitor. If an exception is raised, the stack trace will be included in the failure message.
|
29
44
|
|
30
|
-
|
45
|
+
```ruby
|
46
|
+
require 'cronitor'
|
47
|
+
|
48
|
+
Cronitor.job 'warehouse-replenishmenth-report' do
|
49
|
+
ReplenishmentReport.new(Date.today).run()
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
### Sending Telemetry Events
|
54
|
+
|
55
|
+
If you want finer control over when/how [telemetry pings](https://cronitor.io/docs/telemetry-api) are sent,
|
56
|
+
you can instantiate a monitor and call `#ping`.
|
31
57
|
|
32
58
|
```ruby
|
33
59
|
require 'cronitor'
|
34
60
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
duration: 5
|
48
|
-
rime_unit: 'seconds'
|
49
|
-
}
|
50
|
-
],
|
51
|
-
note: 'A human-friendly description of this monitor'
|
52
|
-
}
|
53
|
-
my_monitor = Cronitor.new token: 'api_token', opts: monitor_options
|
61
|
+
|
62
|
+
monitor = Cronitor::Monitor.new('heartbeat-monitor')
|
63
|
+
|
64
|
+
monitor.ping # a basic heartbeat event
|
65
|
+
|
66
|
+
# optional params can be passed as kwargs
|
67
|
+
# complete list - https://cronitor.io/docs/telemetry-api#parameters
|
68
|
+
|
69
|
+
monitor.ping(state: 'run', env: 'staging') # a job/process has started in a staging environment
|
70
|
+
|
71
|
+
# a job/process has completed - include metrics for cronitor to record
|
72
|
+
monitor.ping(state: 'complete', metrics: {count: 1000, error_count: 17})
|
54
73
|
```
|
55
74
|
|
56
|
-
|
75
|
+
### Pause, Reset, Delete
|
57
76
|
|
58
77
|
```ruby
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
],
|
78
|
+
require 'cronitor'
|
79
|
+
|
80
|
+
monitor = Cronitor::Monitor.new('heartbeat-monitor')
|
81
|
+
|
82
|
+
monitor.pause(24) # pause alerting for 24 hours
|
83
|
+
monitor.unpause # alias for .pause(0)
|
84
|
+
monitor.ok # manually reset to a passing state alias for monitor.ping({state: ok})
|
85
|
+
monitor.delete # destroy the monitor
|
68
86
|
```
|
69
87
|
|
70
|
-
|
88
|
+
## Create and Update Monitors
|
89
|
+
|
90
|
+
You can create monitors programatically using the `Monitor` object.
|
91
|
+
For details on all of the attributes that can be set see the [Monitor API](https://cronitor.io/docs/monitor-api) documentation.
|
71
92
|
|
72
|
-
Once you’ve created a monitor, you can continue to use the existing instance of the object to ping the monitor that your task status: `run`, `complete`, or `fail`.
|
73
93
|
|
74
94
|
```ruby
|
75
|
-
|
76
|
-
|
77
|
-
|
95
|
+
require 'cronitor'
|
96
|
+
|
97
|
+
monitors = Cronitor::Monitor.put([
|
98
|
+
{
|
99
|
+
type: 'job',
|
100
|
+
key: 'send-customer-invoices',
|
101
|
+
schedule: '0 0 * * *',
|
102
|
+
assertions: [
|
103
|
+
'metric.duration < 5 min'
|
104
|
+
],
|
105
|
+
notify: ['devops-alerts-slack']
|
106
|
+
},
|
107
|
+
{
|
108
|
+
type: 'synthetic',
|
109
|
+
key: 'Orders Api Uptime',
|
110
|
+
schedule: 'every 45 seconds',
|
111
|
+
assertions: [
|
112
|
+
'response.code = 200',
|
113
|
+
'response.time < 1.5s',
|
114
|
+
'response.json "open_orders" < 2000'
|
115
|
+
]
|
116
|
+
}
|
117
|
+
])
|
78
118
|
```
|
79
119
|
|
80
|
-
|
120
|
+
You can also manage all of your monitors via a YAML config file.
|
121
|
+
This can be version controlled and synced to Cronitor as part of
|
122
|
+
a deployment process or system update.
|
81
123
|
|
82
|
-
|
124
|
+
```ruby
|
125
|
+
require 'cronitor'
|
83
126
|
|
84
|
-
|
127
|
+
# read config file and set credentials (if included).
|
128
|
+
Cronitor.read_config('./cronitor.yaml')
|
85
129
|
|
86
|
-
|
87
|
-
|
130
|
+
# sync config file's monitors to Cronitor.
|
131
|
+
Cronitor.apply_config
|
132
|
+
|
133
|
+
# send config file's monitors to Cronitor to validate correctness.
|
134
|
+
# monitors will not be saved.
|
135
|
+
Cronitor.validate_config
|
88
136
|
```
|
89
137
|
|
90
|
-
The aforementioned ping methods can now be used.
|
91
138
|
|
92
|
-
|
139
|
+
The `cronitor.yaml` file accepts the following attributes:
|
140
|
+
|
141
|
+
```yaml
|
142
|
+
api_key: 'optionally read Cronitor api_key from here'
|
143
|
+
api_version: 'optionally read Cronitor api_version from here'
|
144
|
+
environment: 'optionally set an environment for telemetry pings'
|
145
|
+
|
146
|
+
# configure all of your monitors with type "job"
|
147
|
+
# you may omit the type attribute and the key
|
148
|
+
# of each object will be set as the monitor key
|
149
|
+
jobs:
|
150
|
+
nightly-database-backup:
|
151
|
+
schedule: 0 0 * * *
|
152
|
+
notify:
|
153
|
+
- devops-alert-pagerduty
|
154
|
+
assertions:
|
155
|
+
- metric.duration < 5 minutes
|
156
|
+
|
157
|
+
send-welcome-email:
|
158
|
+
schedule: every 10 minutes
|
159
|
+
assertions:
|
160
|
+
- metric.count > 0
|
161
|
+
- metric.duration < 30 seconds
|
162
|
+
|
163
|
+
# configure all of your monitors with type "synthetic"
|
164
|
+
synthetics:
|
165
|
+
cronitor-homepage:
|
166
|
+
request:
|
167
|
+
url: https://cronitor.io
|
168
|
+
regions:
|
169
|
+
- us-east-1
|
170
|
+
- eu-central-1
|
171
|
+
- ap-northeast-1
|
172
|
+
assertions:
|
173
|
+
- response.code = 200
|
174
|
+
- response.time < 2s
|
175
|
+
|
176
|
+
cronitor-telemetry-api:
|
177
|
+
request:
|
178
|
+
url: https://cronitor.link/ping
|
179
|
+
assertions:
|
180
|
+
- response.body contains ok
|
181
|
+
- response.time < .25s
|
182
|
+
|
183
|
+
events:
|
184
|
+
production-deploy:
|
185
|
+
notify:
|
186
|
+
alerts: ['deploys-slack']
|
187
|
+
events: true # send alert when the event occurs
|
93
188
|
|
94
|
-
|
189
|
+
```
|
95
190
|
|
96
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
97
191
|
|
98
192
|
## Contributing
|
99
193
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
194
|
+
Pull requests and features are happily considered! By participating in this project you agree to abide by the [Code of Conduct](http://contributor-covenant.org/version/2/0).
|
195
|
+
|
196
|
+
### To contribute
|
197
|
+
|
198
|
+
Fork, then clone the repo:
|
199
|
+
|
200
|
+
git clone git@github.com:your-username/cronitor-ruby.git
|
201
|
+
|
202
|
+
|
203
|
+
Set up your machine:
|
204
|
+
|
205
|
+
bin/setup
|
206
|
+
|
207
|
+
|
208
|
+
Make sure the tests pass:
|
209
|
+
|
210
|
+
rake spec
|
211
|
+
|
212
|
+
|
213
|
+
Make your change. You can experiment using:
|
214
|
+
|
215
|
+
bin/console
|
216
|
+
|
217
|
+
|
218
|
+
Add tests for your change. Make the tests pass:
|
219
|
+
|
220
|
+
rake spec
|
221
|
+
|
222
|
+
Push to your fork and [submit a pull request]( https://github.com/cronitorio/cronitor-ruby/compare/)
|
223
|
+
|
224
|
+
|
225
|
+
## Release a new version
|
226
|
+
|
227
|
+
The bump gem makes this easy:
|
228
|
+
|
229
|
+
1. `rake bump:(major|minor|patch|pre)`
|
230
|
+
2. `rake release`
|
data/Rakefile
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bundler/gem_tasks'
|
2
4
|
require 'rspec/core/rake_task'
|
3
5
|
require 'bump/tasks'
|
6
|
+
require 'rubocop/rake_task'
|
4
7
|
|
5
8
|
RSpec::Core::RakeTask.new(:spec)
|
6
9
|
|
10
|
+
RuboCop::RakeTask.new do |task|
|
11
|
+
task.requires << 'rubocop-rake'
|
12
|
+
task.requires << 'rubocop-rspec'
|
13
|
+
end
|
14
|
+
|
7
15
|
task default: :spec
|
data/bin/console
CHANGED
data/cronitor.gemspec
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
5
|
require 'cronitor/version'
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
8
|
spec.name = 'cronitor'
|
7
9
|
spec.version = Cronitor::VERSION
|
8
|
-
spec.authors = ['Jeff Byrnes']
|
9
|
-
spec.email = ['thejeffbyrnes@gmail.com']
|
10
|
+
spec.authors = ['Jeff Byrnes', 'August Flanagan']
|
11
|
+
spec.email = ['thejeffbyrnes@gmail.com', 'august@cronitor.io']
|
10
12
|
|
11
13
|
spec.summary = 'An interface for the Cronitor API'
|
12
|
-
spec.homepage = 'https://github.com/
|
14
|
+
spec.homepage = 'https://github.com/cronitorio/cronitor-ruby'
|
15
|
+
|
16
|
+
spec.required_ruby_version = '>= 2.4'
|
13
17
|
|
14
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
15
19
|
f.match(%r{^(test|spec|features)/})
|
@@ -18,14 +22,16 @@ Gem::Specification.new do |spec|
|
|
18
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
23
|
spec.require_paths = ['lib']
|
20
24
|
|
21
|
-
spec.
|
22
|
-
spec.add_dependency 'hashie', '~> 3.4'
|
25
|
+
spec.add_runtime_dependency 'httparty'
|
23
26
|
|
24
|
-
spec.add_development_dependency 'bundler', '~> 1.10'
|
25
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
-
spec.add_development_dependency 'rspec', '~> 3.3'
|
27
|
-
spec.add_development_dependency 'pry', '~> 0.10'
|
28
|
-
spec.add_development_dependency 'webmock', '~> 1.21'
|
29
|
-
spec.add_development_dependency 'sinatra', '~> 1.4'
|
30
27
|
spec.add_development_dependency 'bump', '~> 0.1'
|
28
|
+
spec.add_development_dependency 'bundler'
|
29
|
+
spec.add_development_dependency 'pry', '~> 0.10'
|
30
|
+
spec.add_development_dependency 'rake'
|
31
|
+
spec.add_development_dependency 'rspec', '~> 3.3'
|
32
|
+
spec.add_development_dependency 'rubocop', '~> 1.8'
|
33
|
+
spec.add_development_dependency 'rubocop-rake', '~> 0.5.1'
|
34
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.1'
|
35
|
+
spec.add_development_dependency 'sinatra', '~> 2.0'
|
36
|
+
spec.add_development_dependency 'webmock', '~> 3.1'
|
31
37
|
end
|
data/lib/cronitor.rb
CHANGED
@@ -1,107 +1,87 @@
|
|
1
|
-
|
2
|
-
require 'cronitor/error'
|
3
|
-
require 'net/http'
|
4
|
-
require 'unirest'
|
5
|
-
require 'hashie'
|
6
|
-
|
7
|
-
Unirest.default_header 'Accept', 'application/json'
|
8
|
-
Unirest.default_header 'Content-Type', 'application/json'
|
1
|
+
# frozen_string_literal: true
|
9
2
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
3
|
+
require 'logger'
|
4
|
+
require 'json'
|
5
|
+
require 'httparty'
|
6
|
+
require 'socket'
|
7
|
+
require 'time'
|
8
|
+
require 'yaml'
|
14
9
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
10
|
+
require 'cronitor/config'
|
11
|
+
require 'cronitor/error'
|
12
|
+
require 'cronitor/version'
|
13
|
+
require 'cronitor/monitor'
|
14
|
+
|
15
|
+
module Cronitor
|
16
|
+
def self.read_config(path = nil, output: false)
|
17
|
+
Cronitor.config = path || Cronitor.config
|
18
|
+
unless Cronitor.config
|
19
|
+
raise ConfigurationError.new(
|
20
|
+
"Must include a path by setting Cronitor.config or passing a path to read_config e.g. \
|
21
|
+
Cronitor.read_config('./cronitor.yaml')"
|
25
22
|
)
|
26
23
|
end
|
27
24
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
human_readable @opts[:rules] if @opts.key? :rules
|
25
|
+
conf = YAML.safe_load(File.read(Cronitor.config))
|
26
|
+
conf.each do |k, _v|
|
27
|
+
raise ConfigurationError.new("Invalid configuration variable: #{k}") unless Cronitor::YAML_KEYS.include?(k)
|
32
28
|
end
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def create
|
38
|
-
response = Unirest.post(
|
39
|
-
"#{API_URL}/monitors",
|
40
|
-
auth: { user: token },
|
41
|
-
parameters: opts.to_json
|
42
|
-
)
|
30
|
+
Cronitor.api_key = conf[:api_key] if conf[:api_key]
|
31
|
+
Cronitor.api_version = conf[:api_version] if conf[:api_version]
|
32
|
+
Cronitor.environment = conf[:environment] if conf[:environment]
|
43
33
|
|
44
|
-
|
45
|
-
end
|
34
|
+
return unless output
|
46
35
|
|
47
|
-
|
48
|
-
|
49
|
-
"#{
|
50
|
-
|
51
|
-
|
52
|
-
return false unless response.code == 200
|
36
|
+
monitors = []
|
37
|
+
Cronitor::MONITOR_TYPES.each do |t|
|
38
|
+
plural_t = "#{t}s"
|
39
|
+
to_parse = conf[t] || conf[plural_t] || nil
|
40
|
+
next unless to_parse
|
53
41
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
def ping(type, msg = nil)
|
60
|
-
url = "#{PING_URL}/#{code}/#{type}"
|
61
|
-
url += "?msg=#{URI.escape msg}" if type == 'fail' && !msg.nil?
|
62
|
-
|
63
|
-
response = Unirest.get url
|
64
|
-
valid? response
|
65
|
-
end
|
42
|
+
unless to_parse.is_a?(Hash)
|
43
|
+
raise ConfigurationError.new('A Hash with keys corresponding to monitor keys is expected.')
|
44
|
+
end
|
66
45
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
"#{rule[:time_unit]}"
|
46
|
+
to_parse.each do |key, m|
|
47
|
+
m['key'] = key
|
48
|
+
m['type'] = t
|
49
|
+
monitors << m
|
72
50
|
end
|
73
51
|
end
|
52
|
+
conf['monitors'] = monitors
|
53
|
+
conf
|
74
54
|
end
|
75
55
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
56
|
+
def self.apply_config(rollback: false)
|
57
|
+
conf = read_config(output: true)
|
58
|
+
monitors = Monitor.put(monitors: conf.fetch('monitors', []), rollback: rollback)
|
59
|
+
puts("#{monitors.length} monitors #{rollback ? 'validated' : 'synced to Cronitor'}.")
|
60
|
+
rescue ValidationError => e
|
61
|
+
Cronitor.logger.error(e)
|
62
|
+
end
|
81
63
|
|
82
|
-
|
64
|
+
def self.validate_config
|
65
|
+
apply_config(rollback: true)
|
83
66
|
end
|
84
67
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
68
|
+
def self.job(key, &block)
|
69
|
+
monitor = Monitor.new(key)
|
70
|
+
series = Time.now.to_f
|
71
|
+
monitor.ping(state: 'run', series: series)
|
72
|
+
|
73
|
+
begin
|
74
|
+
block.call
|
75
|
+
monitor.ping(state: 'complete', series: series)
|
76
|
+
rescue StandardError => e
|
77
|
+
monitor.ping(state: 'fail', message: e.message[[0, e.message.length - 1600].max..-1], series: series)
|
78
|
+
raise e
|
94
79
|
end
|
95
|
-
|
96
|
-
msg.join ' '
|
97
80
|
end
|
98
81
|
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
fail(
|
103
|
-
Cronitor::Error,
|
104
|
-
"Something else has gone awry. HTTP status: #{response.code}"
|
105
|
-
)
|
82
|
+
def self.monitor_api_url
|
83
|
+
'https://cronitor.io/api/monitors'
|
106
84
|
end
|
107
85
|
end
|
86
|
+
|
87
|
+
Cronitor.read_config(Cronitor.config) unless Cronitor.config.nil?
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cronitor
|
4
|
+
TYPE_JOB = 'job'
|
5
|
+
TYPE_EVENT = 'event'
|
6
|
+
TYPE_SYNTHETIC = 'synthetic'
|
7
|
+
MONITOR_TYPES = [TYPE_JOB, TYPE_EVENT, TYPE_SYNTHETIC].freeze
|
8
|
+
YAML_KEYS = %w[
|
9
|
+
api_key
|
10
|
+
api_version
|
11
|
+
environment
|
12
|
+
] + MONITOR_TYPES.map { |t| "#{t}s" }
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :api_key, :api_version, :environment, :logger, :config, :_headers
|
16
|
+
|
17
|
+
def configure(&block)
|
18
|
+
block.call(self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
self.api_key = ENV['CRONITOR_API_KEY']
|
23
|
+
self.api_version = ENV['CRONITOR_API_VERSION']
|
24
|
+
self.environment = ENV['CRONITOR_ENVIRONMENT']
|
25
|
+
self.config = ENV['CRONITOR_CONFIG']
|
26
|
+
self.logger = Logger.new($stdout)
|
27
|
+
logger.level = Logger::INFO
|
28
|
+
self._headers = {
|
29
|
+
'Content-Type': 'application/json',
|
30
|
+
'User-Agent': 'cronitor-ruby',
|
31
|
+
'Cronitor-Version': Cronitor.api_version
|
32
|
+
}
|
33
|
+
end
|
data/lib/cronitor/error.rb
CHANGED
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cronitor
|
4
|
+
class Monitor
|
5
|
+
attr_reader :key, :api_key, :api_version, :env
|
6
|
+
|
7
|
+
PING_RETRY_THRESHOLD = 5
|
8
|
+
|
9
|
+
def self.put(opts = {})
|
10
|
+
rollback = opts[:rollback] || false
|
11
|
+
opts.delete(:rollback)
|
12
|
+
|
13
|
+
monitors = opts[:monitors] || [opts]
|
14
|
+
|
15
|
+
resp = HTTParty.put(
|
16
|
+
Cronitor.monitor_api_url,
|
17
|
+
basic_auth: {
|
18
|
+
username: Cronitor.api_key,
|
19
|
+
password: ''
|
20
|
+
},
|
21
|
+
body: {
|
22
|
+
monitors: monitors,
|
23
|
+
rollback: rollback
|
24
|
+
}.to_json,
|
25
|
+
headers: Cronitor._headers,
|
26
|
+
timeout: 10
|
27
|
+
)
|
28
|
+
|
29
|
+
case resp.code
|
30
|
+
when 200
|
31
|
+
out = []
|
32
|
+
data = JSON.parse(resp.body)
|
33
|
+
|
34
|
+
(data['monitors'] || []).each do |md|
|
35
|
+
m = Monitor.new(md['key'])
|
36
|
+
m.data = Cronitor.symbolize_keys(md)
|
37
|
+
out << m
|
38
|
+
end
|
39
|
+
out.length == 1 ? out[0] : out
|
40
|
+
when 400
|
41
|
+
raise ValidationError.new(resp.body)
|
42
|
+
else
|
43
|
+
raise Error.new("Error connecting to Cronitor: #{resp.code}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.delete(key)
|
48
|
+
resp = HTTParty.delete(
|
49
|
+
"#{Cronitor.monitor_api_url}/#{key}",
|
50
|
+
timeout: 10,
|
51
|
+
basic_auth: {
|
52
|
+
username: Cronitor.api_key,
|
53
|
+
password: ''
|
54
|
+
},
|
55
|
+
headers: Cronitor._headers
|
56
|
+
)
|
57
|
+
if resp.code != 204
|
58
|
+
Cronitor.logger.error("Error deleting monitor: #{key}")
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(key, api_key: nil, env: nil)
|
65
|
+
@key = key
|
66
|
+
@api_key = api_key || Cronitor.api_key
|
67
|
+
@env = env || Cronitor.environment
|
68
|
+
end
|
69
|
+
|
70
|
+
def data
|
71
|
+
return @data if defined?(@data)
|
72
|
+
|
73
|
+
@data = fetch
|
74
|
+
@data
|
75
|
+
end
|
76
|
+
|
77
|
+
def data=(data)
|
78
|
+
@data = Cronitor.symbolize_keys(data)
|
79
|
+
end
|
80
|
+
|
81
|
+
def ping(params = {})
|
82
|
+
retry_count = params[:retry_count] || 0
|
83
|
+
if api_key.nil?
|
84
|
+
Cronitor.logger.error('No API key detected. Set Cronitor.api_key or initialize Monitor with an api_key:')
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
begin
|
89
|
+
ping_url = ping_api_url
|
90
|
+
ping_url = fallback_ping_api_url if retry_count > (PING_RETRY_THRESHOLD / 2)
|
91
|
+
|
92
|
+
response = HTTParty.get(
|
93
|
+
ping_url,
|
94
|
+
query: clean_params(params),
|
95
|
+
timeout: 5,
|
96
|
+
headers: Cronitor._headers,
|
97
|
+
query_string_normalizer: lambda do |query|
|
98
|
+
query.compact!
|
99
|
+
metrics = query[:metric]
|
100
|
+
query.delete(:metric)
|
101
|
+
out = query.map { |k, v| "#{k}=#{v}" }
|
102
|
+
out += metrics.map { |m| "metric=#{m}" } unless metrics.nil?
|
103
|
+
out.join('&')
|
104
|
+
end
|
105
|
+
# query_string_normalizer for metrics. instead of metric[]=foo:1 we want metric=foo:1
|
106
|
+
)
|
107
|
+
|
108
|
+
if response.code != 200
|
109
|
+
Cronitor.logger.error("Cronitor Telemetry Error: #{response.code}")
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
true
|
113
|
+
rescue StandardError => e
|
114
|
+
# rescue instances of StandardError i.e. Timeout::Error, SocketError, etc
|
115
|
+
Cronitor.logger.error("Cronitor Telemetry Error: #{e}")
|
116
|
+
return false if retry_count >= Monitor::PING_RETRY_THRESHOLD
|
117
|
+
|
118
|
+
# apply a backoff before sending the next ping
|
119
|
+
sleep(retry_count * 1.5 * rand)
|
120
|
+
ping(params.merge(retry_count: params[:retry_count] + 1))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def ok
|
125
|
+
ping(state: 'ok')
|
126
|
+
end
|
127
|
+
|
128
|
+
def pause(hours = nil)
|
129
|
+
pause_url = "#{monitor_api_url}/pause"
|
130
|
+
pause_url += "/#{hours}" unless hours.nil?
|
131
|
+
|
132
|
+
resp = HTTParty.get(
|
133
|
+
pause_url,
|
134
|
+
timeout: 5,
|
135
|
+
headers: Cronitor._headers,
|
136
|
+
basic_auth: {
|
137
|
+
username: api_key,
|
138
|
+
password: ''
|
139
|
+
}
|
140
|
+
)
|
141
|
+
puts(resp.code)
|
142
|
+
resp.code == 200
|
143
|
+
end
|
144
|
+
|
145
|
+
def unpause
|
146
|
+
pause(0)
|
147
|
+
end
|
148
|
+
|
149
|
+
def ping_api_url
|
150
|
+
"https://cronitor.link/p/#{api_key}/#{key}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def fallback_ping_api_url
|
154
|
+
"https://cronitor.io/p/#{api_key}/#{key}"
|
155
|
+
end
|
156
|
+
|
157
|
+
def monitor_api_url
|
158
|
+
"#{Cronitor.monitor_api_url}/#{key}"
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def fetch
|
164
|
+
unless api_key
|
165
|
+
Cronitor.logger.error(
|
166
|
+
'No API key detected. Set Cronitor.api_key or initialize Monitor with the api_key kwarg'
|
167
|
+
)
|
168
|
+
return
|
169
|
+
end
|
170
|
+
|
171
|
+
HTTParty.get(monitor_api_url, timeout: 10, headers: Cronitor._headers, format: :json)
|
172
|
+
end
|
173
|
+
|
174
|
+
def clean_params(params)
|
175
|
+
{
|
176
|
+
state: params.fetch(:state, nil),
|
177
|
+
message: params.fetch(:message, nil),
|
178
|
+
series: params.fetch(:series, nil),
|
179
|
+
host: params.fetch(:host, Socket.gethostname),
|
180
|
+
metric: params[:metrics] ? params[:metrics].map { |k, v| "#{k}:#{v}" } : nil,
|
181
|
+
stamp: Time.now.to_f,
|
182
|
+
env: params.fetch(:env, env)
|
183
|
+
}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.symbolize_keys(obj)
|
188
|
+
obj.inject({}) do |memo, (k, v)|
|
189
|
+
memo[k.to_sym] = v
|
190
|
+
memo
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
data/lib/cronitor/version.rb
CHANGED
metadata
CHANGED
@@ -1,71 +1,86 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cronitor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Byrnes
|
8
|
-
|
8
|
+
- August Flanagan
|
9
|
+
autorequire:
|
9
10
|
bindir: exe
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2021-01-22 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
15
|
+
name: httparty
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- - "
|
18
|
+
- - ">="
|
18
19
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
+
version: '0'
|
20
21
|
type: :runtime
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- - "
|
25
|
+
- - ">="
|
25
26
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
27
|
+
version: '0'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
29
|
+
name: bump
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
32
|
- - "~>"
|
32
33
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
type: :
|
34
|
+
version: '0.1'
|
35
|
+
type: :development
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
39
|
- - "~>"
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
+
version: '0.1'
|
41
42
|
- !ruby/object:Gem::Dependency
|
42
43
|
name: bundler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: pry
|
43
58
|
requirement: !ruby/object:Gem::Requirement
|
44
59
|
requirements:
|
45
60
|
- - "~>"
|
46
61
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
62
|
+
version: '0.10'
|
48
63
|
type: :development
|
49
64
|
prerelease: false
|
50
65
|
version_requirements: !ruby/object:Gem::Requirement
|
51
66
|
requirements:
|
52
67
|
- - "~>"
|
53
68
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
69
|
+
version: '0.10'
|
55
70
|
- !ruby/object:Gem::Dependency
|
56
71
|
name: rake
|
57
72
|
requirement: !ruby/object:Gem::Requirement
|
58
73
|
requirements:
|
59
|
-
- - "
|
74
|
+
- - ">="
|
60
75
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
76
|
+
version: '0'
|
62
77
|
type: :development
|
63
78
|
prerelease: false
|
64
79
|
version_requirements: !ruby/object:Gem::Requirement
|
65
80
|
requirements:
|
66
|
-
- - "
|
81
|
+
- - ">="
|
67
82
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
83
|
+
version: '0'
|
69
84
|
- !ruby/object:Gem::Dependency
|
70
85
|
name: rspec
|
71
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,72 +96,89 @@ dependencies:
|
|
81
96
|
- !ruby/object:Gem::Version
|
82
97
|
version: '3.3'
|
83
98
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
99
|
+
name: rubocop
|
85
100
|
requirement: !ruby/object:Gem::Requirement
|
86
101
|
requirements:
|
87
102
|
- - "~>"
|
88
103
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
104
|
+
version: '1.8'
|
90
105
|
type: :development
|
91
106
|
prerelease: false
|
92
107
|
version_requirements: !ruby/object:Gem::Requirement
|
93
108
|
requirements:
|
94
109
|
- - "~>"
|
95
110
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
111
|
+
version: '1.8'
|
97
112
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
113
|
+
name: rubocop-rake
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 0.5.1
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.5.1
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rubocop-rspec
|
99
128
|
requirement: !ruby/object:Gem::Requirement
|
100
129
|
requirements:
|
101
130
|
- - "~>"
|
102
131
|
- !ruby/object:Gem::Version
|
103
|
-
version: '1
|
132
|
+
version: '2.1'
|
104
133
|
type: :development
|
105
134
|
prerelease: false
|
106
135
|
version_requirements: !ruby/object:Gem::Requirement
|
107
136
|
requirements:
|
108
137
|
- - "~>"
|
109
138
|
- !ruby/object:Gem::Version
|
110
|
-
version: '1
|
139
|
+
version: '2.1'
|
111
140
|
- !ruby/object:Gem::Dependency
|
112
141
|
name: sinatra
|
113
142
|
requirement: !ruby/object:Gem::Requirement
|
114
143
|
requirements:
|
115
144
|
- - "~>"
|
116
145
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
146
|
+
version: '2.0'
|
118
147
|
type: :development
|
119
148
|
prerelease: false
|
120
149
|
version_requirements: !ruby/object:Gem::Requirement
|
121
150
|
requirements:
|
122
151
|
- - "~>"
|
123
152
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
153
|
+
version: '2.0'
|
125
154
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
155
|
+
name: webmock
|
127
156
|
requirement: !ruby/object:Gem::Requirement
|
128
157
|
requirements:
|
129
158
|
- - "~>"
|
130
159
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
160
|
+
version: '3.1'
|
132
161
|
type: :development
|
133
162
|
prerelease: false
|
134
163
|
version_requirements: !ruby/object:Gem::Requirement
|
135
164
|
requirements:
|
136
165
|
- - "~>"
|
137
166
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
139
|
-
description:
|
167
|
+
version: '3.1'
|
168
|
+
description:
|
140
169
|
email:
|
141
170
|
- thejeffbyrnes@gmail.com
|
171
|
+
- august@cronitor.io
|
142
172
|
executables: []
|
143
173
|
extensions: []
|
144
174
|
extra_rdoc_files: []
|
145
175
|
files:
|
146
176
|
- ".editorconfig"
|
177
|
+
- ".github/workflows/publish.yml"
|
178
|
+
- ".github/workflows/test.yml"
|
147
179
|
- ".gitignore"
|
148
180
|
- ".rspec"
|
149
|
-
- ".
|
181
|
+
- ".rubocop.yml"
|
150
182
|
- Gemfile
|
151
183
|
- LICENSE.txt
|
152
184
|
- README.md
|
@@ -155,12 +187,14 @@ files:
|
|
155
187
|
- bin/setup
|
156
188
|
- cronitor.gemspec
|
157
189
|
- lib/cronitor.rb
|
190
|
+
- lib/cronitor/config.rb
|
158
191
|
- lib/cronitor/error.rb
|
192
|
+
- lib/cronitor/monitor.rb
|
159
193
|
- lib/cronitor/version.rb
|
160
|
-
homepage: https://github.com/
|
194
|
+
homepage: https://github.com/cronitorio/cronitor-ruby
|
161
195
|
licenses: []
|
162
196
|
metadata: {}
|
163
|
-
post_install_message:
|
197
|
+
post_install_message:
|
164
198
|
rdoc_options: []
|
165
199
|
require_paths:
|
166
200
|
- lib
|
@@ -168,16 +202,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
168
202
|
requirements:
|
169
203
|
- - ">="
|
170
204
|
- !ruby/object:Gem::Version
|
171
|
-
version: '
|
205
|
+
version: '2.4'
|
172
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
207
|
requirements:
|
174
208
|
- - ">="
|
175
209
|
- !ruby/object:Gem::Version
|
176
210
|
version: '0'
|
177
211
|
requirements: []
|
178
|
-
|
179
|
-
|
180
|
-
signing_key:
|
212
|
+
rubygems_version: 3.1.4
|
213
|
+
signing_key:
|
181
214
|
specification_version: 4
|
182
215
|
summary: An interface for the Cronitor API
|
183
216
|
test_files: []
|
data/.travis.yml
DELETED