cronitor 3.1.0 → 4.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4b5bc9e1efd7fb5247f7f473195589fb4cb822c52a1ee92f1b6c784086016f7
4
- data.tar.gz: f617431b175f6c2e9263fc48fece1af7d7bff634285d703e5d3d57d548f70236
3
+ metadata.gz: 33e095f9fcce446c592729b13d8de07592c2549bdb4b4f19c5c4a09fe6024415
4
+ data.tar.gz: f3d808e0588b6ac2e03dd4b0b79d41320b37f004f4c8858008076a307acfab84
5
5
  SHA512:
6
- metadata.gz: 88a42cddedfea16c32fe0ab3716684343f7c6e8adcfa9cd5018a43e28c6e1e86ba9ced38cfe361e1ecc184dde146f2b8b222320ce17c2e8e1194c4d698d55e20
7
- data.tar.gz: 5ecb82ea549bd5a5ffb5d4298f493e40d3e2569d1517365156315a30f965fbe8b46629daf3bcae9cd029b93ad14c8363d6f06e0f543d8b26a0e270567446dc61
6
+ metadata.gz: 87bb2ea79e7d93ca390296964de090e08f7b010e65aa1626154bd7677c131ab01e896065cacece5701374aacfebc9d7e2823186302245a3dd3d45d1f0069db5d
7
+ data.tar.gz: 035fa6604dbc17370f36d8b4833c2aa0938671cef3e85d600a82b811ac1df1306583870478a23be54e645f339d4fe4c504a73416d015a6bc35d11d88952f021b
@@ -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 CHANGED
@@ -1,15 +1,42 @@
1
1
  ---
2
+ require:
3
+ - rubocop-rake
4
+ - rubocop-rspec
5
+
6
+ inherit_mode:
7
+ merge:
8
+ - Exclude
9
+
10
+ AllCops:
11
+ NewCops: enable
12
+ TargetRubyVersion: 2.5
13
+ Exclude:
14
+ - 'spec/cronitor_spec.rb'
15
+
2
16
  Metrics/AbcSize:
3
17
  Enabled: false
4
18
 
19
+ Metrics/ClassLength:
20
+ Enabled: false
21
+
5
22
  Metrics/BlockLength:
6
23
  Enabled: false
7
24
 
8
25
  Metrics/CyclomaticComplexity:
9
26
  Enabled: false
10
27
 
28
+ Metrics/PerceivedComplexity:
29
+ Enabled: false
30
+
11
31
  Metrics/MethodLength:
12
32
  Enabled: false
13
33
 
14
34
  Style/Documentation:
15
35
  Enabled: false
36
+
37
+ Style/RaiseArgs:
38
+ EnforcedStyle: compact
39
+
40
+ Style/EachWithObject:
41
+ Enabled: false
42
+
data/README.md CHANGED
@@ -1,135 +1,221 @@
1
- # Cronitor
1
+ # Cronitor Ruby Library
2
2
 
3
- [![Travis](https://img.shields.io/travis/evertrue/cronitor.svg)](https://travis-ci.org/evertrue/cronitor)
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
- This gem provides a simple abstraction for the creation and pinging of a Cronitor monitor. For a better understanding of the API this gem talks to, please see [How Cronitor Works](https://cronitor.io/help/how-cronitor-works).
7
+ [Cronitor](https://cronitor.io/) provides dead simple monitoring for cron jobs, daemons, queue workers, websites, APIs, 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. See our [API docs](https://cronitor.io/docs/api) for detailed references on configuring monitors and sending telemetry pings.
8
+
9
+ In this guide:
10
+
11
+ - [Installation](#Installation)
12
+ - [Monitoring Background Jobs](#monitoring-background-jobs)
13
+ - [Sending Telemetry Events](#sending-telemetry-events)
14
+ - [Configuring Monitors](#configuring-monitors)
15
+ - [Package Configuration & Env Vars](#package-configuration)
16
+ - [Contributing](#contributing)
9
17
 
10
18
  ## Installation
11
19
 
12
- Add this line to your application's Gemfile:
20
+ ```
21
+ gem install cronitor
22
+ ```
23
+
24
+ ## Monitoring Background Jobs
25
+
26
+ The `Cronitor#job` helper will send telemetry events before calling your block and after it exits. If your block raises an exception a `fail` event will be sent (and the exception re-raised).
13
27
 
14
28
  ```ruby
15
- gem 'cronitor'
29
+ require 'cronitor'
30
+ Cronitor.api_key = 'api_key_123'
31
+
32
+ Cronitor.job 'warehouse-replenishmenth-report' do
33
+ ReplenishmentReport.new(Date.today).run()
34
+ end
16
35
  ```
17
36
 
18
- And then execute:
37
+ ### Integrating with Sidekiq
38
+ Cronitor provides a [separate library](https://github.com/cronitorio/cronitor-sidekiq) built with this SDK to make Sidekiq integration even easier.
19
39
 
20
- $ bundle
21
40
 
22
- Or install it yourself as:
41
+ ## Sending Telemetry Events
23
42
 
24
- $ gem install cronitor
43
+ If you want finer control over when/how [telemetry pings](https://cronitor.io/docs/telemetry-api) are sent,
44
+ you can instantiate a monitor and call `#ping`.
25
45
 
26
- ## Usage
46
+ ```ruby
47
+ require 'cronitor'
48
+ Cronitor.api_key = 'api_key_123'
49
+
50
+ monitor = Cronitor::Monitor.new('heartbeat-monitor')
51
+
52
+ monitor.ping # a basic heartbeat event
27
53
 
54
+ # optional params can be passed as kwargs
55
+ # complete list - https://cronitor.io/docs/telemetry-api#parameters
28
56
 
29
- ### Configure
57
+ monitor.ping(state: 'run', env: 'staging') # a job/process has started in a staging environment
58
+
59
+ # a job/process has completed - include metrics for cronitor to record
60
+ monitor.ping(state: 'complete', metrics: {count: 1000, error_count: 17})
61
+ ```
30
62
 
31
- You need to set Cronitor Token in order to create a monitor
63
+ ## Configuring Monitors
32
64
 
33
- #### Using configure
65
+ You can configure all of your monitors using a single YAML file. This can be version controlled and synced to Cronitor as part of
66
+ a deployment or build process. For details on all of the attributes that can be set, see the [Monitor API](https://cronitor.io/docs/monitor-api) documentation.
34
67
 
35
68
  ```ruby
36
69
  require 'cronitor'
70
+ Cronitor.api_key = 'api_key_123'
37
71
 
38
- Cronitor.configure do |cronitor|
39
- cronitor.default_token = 'token' # default token to be re-used by cronitor
40
- end
72
+ # read config file and set credentials (if included).
73
+ Cronitor.read_config('./cronitor.yaml')
74
+
75
+ # sync config file's monitors to Cronitor.
76
+ Cronitor.apply_config
77
+
78
+ # send config file's monitors to Cronitor to validate correctness.
79
+ # monitors will not be saved.
80
+ Cronitor.validate_config
41
81
  ```
42
82
 
43
- #### Using ENV
83
+ The `cronitor.yaml` file includes three top level keys `jobs`, `checks`, `heartbeats`. You can configure monitors under each key by defining [monitors](https://cronitor.io/docs/monitor-api#attributes).
84
+
85
+ ```yaml
86
+ jobs:
87
+ nightly-database-backup:
88
+ schedule: 0 0 * * *
89
+ notify:
90
+ - devops-alert-pagerduty
91
+ assertions:
92
+ - metric.duration < 5 minutes
93
+
94
+ send-welcome-email:
95
+ schedule: every 10 minutes
96
+ assertions:
97
+ - metric.count > 0
98
+ - metric.duration < 30 seconds
99
+
100
+ check:
101
+ cronitor-homepage:
102
+ request:
103
+ url: https://cronitor.io
104
+ regions:
105
+ - us-east-1
106
+ - eu-central-1
107
+ - ap-northeast-1
108
+ assertions:
109
+ - response.code = 200
110
+ - response.time < 2s
111
+
112
+ cronitor-telemetry-api:
113
+ request:
114
+ url: https://cronitor.link/ping
115
+ assertions:
116
+ - response.body contains ok
117
+ - response.time < .25s
118
+
119
+ heartbeats:
120
+ production-deploy:
121
+ notify:
122
+ alerts: ['deploys-slack']
123
+ events: true # send alert when the event occurs
44
124
 
45
125
  ```
46
- # .env
47
- CRONITOR_TOKEN: token
48
126
 
49
- # bash
50
- export CRONITOR_TOKEN='token'
127
+ You can also create and update monitors by calling `Monitor.put`.
128
+
129
+ ```ruby
130
+ require 'cronitor'
131
+
132
+ monitors = Cronitor::Monitor.put([
133
+ {
134
+ type: 'job',
135
+ key: 'send-customer-invoices',
136
+ schedule: '0 0 * * *',
137
+ assertions: [
138
+ 'metric.duration < 5 min'
139
+ ],
140
+ notify: ['devops-alerts-slack']
141
+ },
142
+ {
143
+ type: 'check',
144
+ key: 'Cronitor Homepage',
145
+ request: {
146
+ url: 'https://cronitor.io'
147
+ },
148
+ schedule: 'every 45 seconds',
149
+ assertions: [
150
+ 'response.code = 200',
151
+ 'response.time < 600ms',
152
+ ]
153
+ }
154
+ ])
51
155
  ```
52
156
 
53
- ### Creating a Monitor
157
+ ### Pause, Reset, Delete
158
+
159
+ ```ruby
160
+ require 'cronitor'
54
161
 
55
- A Cronitor monitor (hereafter referred to only as a monitor for brevity) is created if it does not already exist, and its ID returned.
162
+ monitor = Cronitor::Monitor.new('heartbeat-monitor')
56
163
 
57
- Please see the [Cronitor Monitor API docs](https://cronitor.io/docs/monitor-api) for details of all the possible monitor options.
164
+ monitor.pause(24) # pause alerting for 24 hours
165
+ monitor.unpause # alias for .pause(0)
166
+ monitor.ok # manually reset to a passing state alias for monitor.ping({state: ok})
167
+ monitor.delete # destroy the monitor
168
+ ```
58
169
 
59
- Example of creating a heartbeat monitor:
170
+ ## Package Configuration
171
+
172
+ 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` and an `environment`. If not provided, your account default is used. These can also be supplied using the environment variables `CRONITOR_API_KEY`, `CRONITOR_API_VERSION`, `CRONITOR_ENVIRONMENT`.
60
173
 
61
174
  ```ruby
62
175
  require 'cronitor'
63
176
 
64
- monitor_options = {
65
- name: 'My Fancy Monitor',
66
- type: 'heartbeat', # Optional: the gem defaults to this; the other value, 'healthcheck', is not yet supported by this gem
67
- notifications: {
68
- emails: ['test@example.com'],
69
- slack: [],
70
- pagerduty: [],
71
- phones: [],
72
- webhooks: []
73
- },
74
- rules: [
75
- {
76
- rule_type: 'run_ping_not_received',
77
- value: 5,
78
- time_unit: 'seconds'
79
- }
80
- ],
81
- note: 'A human-friendly description of this monitor'
82
- }
83
-
84
- # The token parameter is optional; if omittted, ENV['CRONITOR_TOKEN'] will be used if not configured
85
- my_monitor = Cronitor.new token: 'api_token', opts: monitor_options
177
+ # your api keys can found here - https://cronitor.io/settings
178
+ Cronitor.api_key = 'apiKey123'
179
+ Cronitor.api_version = '2020-10-01'
180
+ Cronitor.environment = 'cluster_1_prod'
86
181
  ```
87
182
 
88
- ### Updating an existing monitor
183
+ ## Contributing
89
184
 
90
- Currently this gem does not support updating or deleting an existing monitor.
185
+ 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).
91
186
 
92
- ### Pinging a Monitor
187
+ ### To contribute
93
188
 
94
- 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`.
189
+ Fork, then clone the repo:
95
190
 
96
- ```ruby
97
- my_monitor.ping 'run'
98
- my_monitor.ping 'complete'
99
- my_monitor.ping 'fail', 'A short description of the failure'
100
- ```
191
+ git clone git@github.com:your-username/cronitor-ruby.git
101
192
 
102
- ### Pinging a monitor when you have a Cronitor code
103
193
 
104
- You may already have the code for a monitor, in which case, the expense of `Cronitor.create` may seem unnecessary (since it makes an HTTP request to check if a monitor exists, and you already know it does).
194
+ Set up your machine:
105
195
 
106
- Cronitor does not require a token for pinging a monitor unless you have enabled Ping API authentication in your account settings. At the moment, this gem does not support Ping API auth.
196
+ bin/setup
107
197
 
108
- In that case:
109
198
 
110
- ```ruby
111
- my_monitor = Cronitor.new code: 'abcd'
112
- ```
199
+ Make sure the tests pass:
113
200
 
114
- The aforementioned ping methods can now be used.
201
+ rake spec
115
202
 
116
- ## Development
117
203
 
118
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
204
+ Make your change. You can experiment using:
119
205
 
120
- 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).
206
+ bin/console
121
207
 
122
- ## Contributing
123
208
 
124
- 1. Fork it ( https://github.com/evertrue/cronitor/fork )
125
- 2. Create your feature branch (`git checkout -b my-new-feature`)
126
- 3. Commit your changes (`git commit -am 'Add some feature'`)
127
- 4. Push to the branch (`git push origin my-new-feature`)
128
- 5. Create a new Pull Request
209
+ Add tests for your change. Make the tests pass:
210
+
211
+ rake spec
212
+
213
+ Push to your fork and [submit a pull request]( https://github.com/cronitorio/cronitor-ruby/compare/)
214
+
129
215
 
130
216
  ## Release a new version
131
217
 
132
- The `bump` gem makes this easy:
218
+ The bump gem makes this easy:
133
219
 
134
220
  1. `rake bump:(major|minor|patch|pre)`
135
221
  2. `rake release`
data/Rakefile CHANGED
@@ -3,7 +3,13 @@
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
5
  require 'bump/tasks'
6
+ require 'rubocop/rake_task'
6
7
 
7
8
  RSpec::Core::RakeTask.new(:spec)
8
9
 
10
+ RuboCop::RakeTask.new do |task|
11
+ task.requires << 'rubocop-rake'
12
+ task.requires << 'rubocop-rspec'
13
+ end
14
+
9
15
  task default: :spec
data/cronitor.gemspec CHANGED
@@ -7,11 +7,13 @@ require 'cronitor/version'
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'cronitor'
9
9
  spec.version = Cronitor::VERSION
10
- spec.authors = ['Jeff Byrnes']
11
- spec.email = ['thejeffbyrnes@gmail.com']
10
+ spec.authors = ['Jeff Byrnes', 'August Flanagan']
11
+ spec.email = ['thejeffbyrnes@gmail.com', 'august@cronitor.io']
12
12
 
13
13
  spec.summary = 'An interface for the Cronitor API'
14
- spec.homepage = 'https://github.com/evertrue/cronitor'
14
+ spec.homepage = 'https://github.com/cronitorio/cronitor-ruby'
15
+
16
+ spec.required_ruby_version = '>= 2.5'
15
17
 
16
18
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
19
  f.match(%r{^(test|spec|features)/})
@@ -20,11 +22,16 @@ Gem::Specification.new do |spec|
20
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
23
  spec.require_paths = ['lib']
22
24
 
25
+ spec.add_runtime_dependency 'httparty'
26
+
23
27
  spec.add_development_dependency 'bump', '~> 0.1'
24
28
  spec.add_development_dependency 'bundler'
25
29
  spec.add_development_dependency 'pry', '~> 0.10'
26
30
  spec.add_development_dependency 'rake'
27
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'
28
35
  spec.add_development_dependency 'sinatra', '~> 2.0'
29
36
  spec.add_development_dependency 'webmock', '~> 3.1'
30
37
  end
data/lib/cronitor.rb CHANGED
@@ -1,138 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cronitor/version'
4
- require 'cronitor/error'
3
+ require 'logger'
5
4
  require 'json'
6
- require 'net/http'
7
- require 'uri'
8
-
9
- class Cronitor
10
- attr_accessor :token, :opts, :code
11
- API_URL = 'https://cronitor.io/v3'
12
- PING_URL = 'https://cronitor.link'
13
-
14
- class << self
15
- attr_accessor :default_token
16
-
17
- def configure(&block)
18
- block.call(self)
19
- end
20
- end
21
-
22
- def initialize(token: ENV['CRONITOR_TOKEN'], opts: {}, code: nil)
23
- @token = token || self.class.default_token
24
- @opts = opts
25
- @code = code
5
+ require 'httparty'
6
+ require 'socket'
7
+ require 'time'
8
+ require 'yaml'
26
9
 
27
- if @token.nil? && @code.nil?
28
- raise(
29
- Cronitor::Error,
30
- 'Either a Cronitor API token or an existing monitor code must be ' \
31
- 'provided'
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')"
32
22
  )
33
23
  end
34
24
 
35
- if @opts
36
- @opts = symbolize_keys @opts
37
-
38
- exists? @opts[:name] if @opts.key? :name
39
-
40
- # README: Per Cronitor API v2, we need to specify a type. The "heartbeat"
41
- # type corresponds to what the v1 API offered by default
42
- # We allow other values to be injected, and let the API handle
43
- # any errors.
44
- @opts[:type] = 'heartbeat' unless @opts[:type]
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)
45
28
  end
46
29
 
47
- create if @code.nil?
48
- end
49
-
50
- def create
51
- uri = URI.parse "#{API_URL}/monitors"
52
-
53
- http = Net::HTTP.new uri.host, uri.port
54
- http.use_ssl = uri.scheme == 'https'
55
-
56
- request = Net::HTTP::Post.new uri.path, default_headers
57
- request.basic_auth token, nil
58
- request.content_type = 'application/json'
59
- request.body = JSON.generate opts
60
-
61
- response = http.request request
62
-
63
- @code = JSON.parse(response.body).fetch 'code' if valid? response
64
- end
65
-
66
- def exists?(name)
67
- uri = URI.parse "#{API_URL}/monitors/#{URI.escape name}"
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]
68
33
 
69
- http = Net::HTTP.new uri.host, uri.port
70
- http.use_ssl = uri.scheme == 'https'
34
+ return unless output
71
35
 
72
- request = Net::HTTP::Get.new uri.path, default_headers
73
- request.basic_auth token, nil
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
74
41
 
75
- response = http.request request
76
-
77
- return false unless response.is_a? Net::HTTPSuccess
78
-
79
- @code = JSON.parse(response.body).fetch 'code'
80
- @opts = JSON.parse(response.body)
81
-
82
- true
83
- end
42
+ unless to_parse.is_a?(Hash)
43
+ raise ConfigurationError.new('A Hash with keys corresponding to monitor keys is expected.')
44
+ end
84
45
 
85
- def ping(type, msg = nil)
86
- uri = URI.parse "#{PING_URL}/#{URI.escape code}/#{URI.escape type}"
87
- if %w[run complete fail].include?(type) && !msg.nil?
88
- uri.query = URI.encode_www_form 'msg' => msg
46
+ to_parse.each do |key, m|
47
+ m['key'] = key
48
+ m['type'] = t
49
+ monitors << m
50
+ end
89
51
  end
90
-
91
- http = Net::HTTP.new uri.host, uri.port
92
- http.use_ssl = uri.scheme == 'https'
93
-
94
- request = Net::HTTP::Get.new uri, default_headers
95
-
96
- response = http.request request
97
-
98
- valid? response
52
+ conf['monitors'] = monitors
53
+ conf
99
54
  end
100
55
 
101
- private
102
-
103
- def valid?(response)
104
- return true if response.is_a? Net::HTTPSuccess
105
-
106
- msg = if response.content_type.match? 'json'
107
- error_msg JSON.parse(response.body)
108
- else
109
- "Something else has gone awry. HTTP status: #{response.code}"
110
- end
111
-
112
- raise Cronitor::Error, msg
56
+ def self.apply_config(rollback: false)
57
+ conf = read_config(output: true)
58
+ # allow a significantly longer timeout on requests that are sending full yaml config
59
+ monitors = Monitor.put(monitors: conf.fetch('monitors', []), rollback: rollback, timeout: 30)
60
+ puts("#{monitors.length} monitors #{rollback ? 'validated' : 'synced to Cronitor'}.")
61
+ rescue ValidationError => e
62
+ Cronitor.logger.error(e)
113
63
  end
114
64
 
115
- def error_msg(body, msg = [])
116
- body.each do |opt, value|
117
- if value.respond_to? 'each'
118
- value.each do |error_msg|
119
- msg << "#{opt}: #{error_msg}"
120
- end
121
- else
122
- msg << "#{opt}: #{value}"
123
- end
124
- end
125
-
126
- msg.join ' '
65
+ def self.validate_config
66
+ apply_config(rollback: true)
127
67
  end
128
68
 
129
- def default_headers
130
- { 'Accept' => 'application/json' }
69
+ def self.job(key, &block)
70
+ monitor = Monitor.new(key)
71
+ series = Time.now.to_f
72
+ monitor.ping(state: 'run', series: series)
73
+
74
+ begin
75
+ block.call
76
+ monitor.ping(state: 'complete', series: series)
77
+ rescue StandardError => e
78
+ monitor.ping(state: 'fail', message: e.message[[0, e.message.length - 1600].max..-1], series: series)
79
+ raise e
80
+ end
131
81
  end
132
82
 
133
- def symbolize_keys(hash)
134
- hash.each_with_object({}) do |(k, v), h|
135
- h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
136
- end
83
+ def self.monitor_api_url
84
+ 'https://cronitor.io/api/monitors'
137
85
  end
138
86
  end
87
+
88
+ 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_HEARTBEAT = 'heartbeat'
6
+ TYPE_CHECK = 'check'
7
+ MONITOR_TYPES = [TYPE_JOB, TYPE_HEARTBEAT, TYPE_CHECK].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
@@ -1,6 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Cronitor
3
+ module Cronitor
4
4
  class Error < StandardError
5
5
  end
6
+
7
+ class ValidationError < Error
8
+ end
9
+
10
+ class ConfigurationError < Error
11
+ end
6
12
  end
@@ -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: opts[: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: 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Cronitor
4
- VERSION = '3.1.0'
3
+ module Cronitor
4
+ VERSION = '4.1.2'
5
5
  end
metadata CHANGED
@@ -1,15 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cronitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 4.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Byrnes
8
+ - August Flanagan
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2020-10-23 00:00:00.000000000 Z
12
+ date: 2021-07-13 00:00:00.000000000 Z
12
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httparty
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
13
28
  - !ruby/object:Gem::Dependency
14
29
  name: bump
15
30
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +95,48 @@ dependencies:
80
95
  - - "~>"
81
96
  - !ruby/object:Gem::Version
82
97
  version: '3.3'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rubocop
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '1.8'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '1.8'
112
+ - !ruby/object:Gem::Dependency
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
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '2.1'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '2.1'
83
140
  - !ruby/object:Gem::Dependency
84
141
  name: sinatra
85
142
  requirement: !ruby/object:Gem::Requirement
@@ -111,15 +168,17 @@ dependencies:
111
168
  description:
112
169
  email:
113
170
  - thejeffbyrnes@gmail.com
171
+ - august@cronitor.io
114
172
  executables: []
115
173
  extensions: []
116
174
  extra_rdoc_files: []
117
175
  files:
118
176
  - ".editorconfig"
177
+ - ".github/workflows/publish.yml"
178
+ - ".github/workflows/test.yml"
119
179
  - ".gitignore"
120
180
  - ".rspec"
121
181
  - ".rubocop.yml"
122
- - ".travis.yml"
123
182
  - Gemfile
124
183
  - LICENSE.txt
125
184
  - README.md
@@ -128,9 +187,11 @@ files:
128
187
  - bin/setup
129
188
  - cronitor.gemspec
130
189
  - lib/cronitor.rb
190
+ - lib/cronitor/config.rb
131
191
  - lib/cronitor/error.rb
192
+ - lib/cronitor/monitor.rb
132
193
  - lib/cronitor/version.rb
133
- homepage: https://github.com/evertrue/cronitor
194
+ homepage: https://github.com/cronitorio/cronitor-ruby
134
195
  licenses: []
135
196
  metadata: {}
136
197
  post_install_message:
@@ -141,7 +202,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
202
  requirements:
142
203
  - - ">="
143
204
  - !ruby/object:Gem::Version
144
- version: '0'
205
+ version: '2.5'
145
206
  required_rubygems_version: !ruby/object:Gem::Requirement
146
207
  requirements:
147
208
  - - ">="
data/.travis.yml DELETED
@@ -1,5 +0,0 @@
1
- ---
2
- language: ruby
3
- rvm:
4
- - 2
5
- - 2.4