cronitor 1.1.5 → 4.1.0

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
- SHA1:
3
- metadata.gz: 4c234da3ffd469dc74eefc0ee50a369c5c3b967c
4
- data.tar.gz: c89a778d71c9b27463777699eed0b2fc21a2cd12
2
+ SHA256:
3
+ metadata.gz: d91bccffc3a53dfe487e4740c00aa83b47fe31c4347a1bdc93e017bbb3467751
4
+ data.tar.gz: 5730c8a4742bab773c1e3582fa4f00298890171e91ef96b83fda8e0a6ebd6470
5
5
  SHA512:
6
- metadata.gz: 958007209685433235f3387c29cddbdb6e976c27cf1539bfa5ce2e7fdd58d0e9e8057c6089ba28635005368ae658cb96370a51c9863f338671aff78860f0d38e
7
- data.tar.gz: 64db58946e2287628ba325387cccb469a07d7bdd6bffbf965b69111f8c5f7dcd172d5dec0a61588f4e5d94b29c823c23a8394ee89563bb5770553be82b09337e
6
+ metadata.gz: '054173538e57d465de2cc25786a42ccdb80c2be0532f0526634153de3fd7c46f428cdeaded4dd84e6169a9aa5717439bab0b1b4cc9672b56b93543ae58183ae3'
7
+ data.tar.gz: 311901851f90644a7d0c46eeed1b2bd821880816090799d1601008e29fa19a4cad2199905f317a9a08225296d579ba054bc87456dadf19fe427f3487c0ac42ce
@@ -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,42 @@
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
+
16
+ Metrics/AbcSize:
17
+ Enabled: false
18
+
19
+ Metrics/ClassLength:
20
+ Enabled: false
21
+
22
+ Metrics/BlockLength:
23
+ Enabled: false
24
+
25
+ Metrics/CyclomaticComplexity:
26
+ Enabled: false
27
+
28
+ Metrics/PerceivedComplexity:
29
+ Enabled: false
30
+
31
+ Metrics/MethodLength:
32
+ Enabled: false
33
+
34
+ Style/Documentation:
35
+ Enabled: false
36
+
37
+ Style/RaiseArgs:
38
+ EnforcedStyle: compact
39
+
40
+ Style/EachWithObject:
41
+ Enabled: false
42
+
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in cronitor.gemspec
data/README.md CHANGED
@@ -1,104 +1,222 @@
1
- # Cronitor
1
+ # Cronitor Ruby Library
2
2
 
3
- [![Travis](https://img.shields.io/travis/evertrue/cronitor.svg)](https://github.com/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
+ - [Command Line Usage](##command-line-usage)
17
+ - [Contributing](##contributing)
9
18
 
10
19
  ## Installation
11
20
 
12
- Add this line to your application's Gemfile:
21
+ ```
22
+ gem install cronitor
23
+ ```
24
+
25
+ ## Monitoring Background Jobs
26
+
27
+ 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).
28
+
29
+ ```ruby
30
+ require 'cronitor'
31
+ Cronitor.api_key = 'api_key_123'
32
+
33
+ Cronitor.job 'warehouse-replenishmenth-report' do
34
+ ReplenishmentReport.new(Date.today).run()
35
+ end
36
+ ```
37
+
38
+ ### Integrating with Sidekiq
39
+ Cronitor provides a [separate library](https://github.com/cronitorio/cronitor-sidekiq) built with this SDK to make Sidekiq integration even easier.
40
+
41
+
42
+ ## Sending Telemetry Events
43
+
44
+ If you want finer control over when/how [telemetry pings](https://cronitor.io/docs/telemetry-api) are sent,
45
+ you can instantiate a monitor and call `#ping`.
13
46
 
14
47
  ```ruby
15
- gem 'cronitor'
48
+ require 'cronitor'
49
+ Cronitor.api_key = 'api_key_123'
50
+
51
+ monitor = Cronitor::Monitor.new('heartbeat-monitor')
52
+
53
+ monitor.ping # a basic heartbeat event
54
+
55
+ # optional params can be passed as kwargs
56
+ # complete list - https://cronitor.io/docs/telemetry-api#parameters
57
+
58
+ monitor.ping(state: 'run', env: 'staging') # a job/process has started in a staging environment
59
+
60
+ # a job/process has completed - include metrics for cronitor to record
61
+ monitor.ping(state: 'complete', metrics: {count: 1000, error_count: 17})
16
62
  ```
17
63
 
18
- And then execute:
64
+ ## Configuring Monitors
65
+
66
+ You can configure all of your monitors using a single YAML file. This can be version controlled and synced to Cronitor as part of
67
+ 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.
68
+
69
+ ```ruby
70
+ require 'cronitor'
71
+ Cronitor.api_key = 'api_key_123'
19
72
 
20
- $ bundle
73
+ # read config file and set credentials (if included).
74
+ Cronitor.read_config('./cronitor.yaml')
21
75
 
22
- Or install it yourself as:
76
+ # sync config file's monitors to Cronitor.
77
+ Cronitor.apply_config
23
78
 
24
- $ gem install cronitor
79
+ # send config file's monitors to Cronitor to validate correctness.
80
+ # monitors will not be saved.
81
+ Cronitor.validate_config
82
+ ```
25
83
 
26
- ## Usage
84
+ The `cronitor.yaml` file includes three top level keys `jobs`, `checks`, `events`. You can configure monitors under each key by defining [monitors](https://cronitor.io/docs/monitor-api#attributes).
85
+
86
+ ```yaml
87
+ jobs:
88
+ nightly-database-backup:
89
+ schedule: 0 0 * * *
90
+ notify:
91
+ - devops-alert-pagerduty
92
+ assertions:
93
+ - metric.duration < 5 minutes
94
+
95
+ send-welcome-email:
96
+ schedule: every 10 minutes
97
+ assertions:
98
+ - metric.count > 0
99
+ - metric.duration < 30 seconds
100
+
101
+ check:
102
+ cronitor-homepage:
103
+ request:
104
+ url: https://cronitor.io
105
+ regions:
106
+ - us-east-1
107
+ - eu-central-1
108
+ - ap-northeast-1
109
+ assertions:
110
+ - response.code = 200
111
+ - response.time < 2s
112
+
113
+ cronitor-telemetry-api:
114
+ request:
115
+ url: https://cronitor.link/ping
116
+ assertions:
117
+ - response.body contains ok
118
+ - response.time < .25s
119
+
120
+ heartbeats:
121
+ production-deploy:
122
+ notify:
123
+ alerts: ['deploys-slack']
124
+ events: true # send alert when the event occurs
27
125
 
28
- ### Creating a Monitor
126
+ ```
29
127
 
30
- A Cronitor monitor (hereafter referred to only as a monitor for brevity) is created if it does not already exist, and its ID returned.
128
+ You can also create and update monitors by calling `Monitor.put`.
31
129
 
32
130
  ```ruby
33
131
  require 'cronitor'
34
132
 
35
- monitor_options = {
36
- name: 'My Fancy Monitor',
37
- notifications: {
38
- emails: ['test@example.com'],
39
- slack: [],
40
- pagerduty: [],
41
- phones: [],
42
- webhooks: []
133
+ monitors = Cronitor::Monitor.put([
134
+ {
135
+ type: 'job',
136
+ key: 'send-customer-invoices',
137
+ schedule: '0 0 * * *',
138
+ assertions: [
139
+ 'metric.duration < 5 min'
140
+ ],
141
+ notify: ['devops-alerts-slack']
43
142
  },
44
- rules: [
45
- {
46
- rule_type: 'not_run_in',
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
143
+ {
144
+ type: 'check',
145
+ key: 'Cronitor Homepage',
146
+ request: {
147
+ url: 'https://cronitor.io'
148
+ },
149
+ schedule: 'every 45 seconds',
150
+ assertions: [
151
+ 'response.code = 200',
152
+ 'response.time < 600ms',
153
+ ]
154
+ }
155
+ ])
54
156
  ```
55
157
 
56
- You may optionally specify a `:human_readable` value for your rule(s), otherwise one will be crafted for you:
158
+ ### Pause, Reset, Delete
57
159
 
58
160
  ```ruby
59
- monitor_options = {
60
- rules: [
61
- {
62
- rule_type: 'not_run_in',
63
- duration: 5
64
- time_unit: 'seconds',
65
- human_readable: 'not_run_in 5 seconds'
66
- }
67
- ],
161
+ require 'cronitor'
162
+
163
+ monitor = Cronitor::Monitor.new('heartbeat-monitor')
164
+
165
+ monitor.pause(24) # pause alerting for 24 hours
166
+ monitor.unpause # alias for .pause(0)
167
+ monitor.ok # manually reset to a passing state alias for monitor.ping({state: ok})
168
+ monitor.delete # destroy the monitor
68
169
  ```
69
170
 
70
- ### Pinging a Monitor
171
+ ## Package Configuration
71
172
 
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`.
173
+ 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`.
73
174
 
74
175
  ```ruby
75
- my_monitor.ping 'run'
76
- my_monitor.ping 'complete'
77
- my_monitor.ping 'fail', 'A short description of the failure'
176
+ require 'cronitor'
177
+
178
+ # your api keys can found here - https://cronitor.io/settings
179
+ Cronitor.api_key = 'apiKey123'
180
+ Cronitor.api_version = '2020-10-01'
181
+ Cronitor.environment = 'cluster_1_prod'
78
182
  ```
79
183
 
80
- ### Pinging a monitor when you have a Cronitor code
184
+ ## Contributing
81
185
 
82
- You may already have the code for a monitor, in which case, the expense of `Cronitor.new` may seem unnecessary (since it makes an HTTP request to check if a monitor exists, and you already know it does).
186
+ 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).
83
187
 
84
- In that case:
188
+ ### To contribute
85
189
 
86
- ```ruby
87
- my_monitor = Cronitor.new code: 'abcd'
88
- ```
190
+ Fork, then clone the repo:
89
191
 
90
- The aforementioned ping methods can now be used.
192
+ git clone git@github.com:your-username/cronitor-ruby.git
91
193
 
92
- ## Development
93
194
 
94
- 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.
195
+ Set up your machine:
95
196
 
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).
197
+ bin/setup
97
198
 
98
- ## Contributing
99
199
 
100
- 1. Fork it ( https://github.com/evertrue/cronitor/fork )
101
- 2. Create your feature branch (`git checkout -b my-new-feature`)
102
- 3. Commit your changes (`git commit -am 'Add some feature'`)
103
- 4. Push to the branch (`git push origin my-new-feature`)
104
- 5. Create a new Pull Request
200
+ Make sure the tests pass:
201
+
202
+ rake spec
203
+
204
+
205
+ Make your change. You can experiment using:
206
+
207
+ bin/console
208
+
209
+
210
+ Add tests for your change. Make the tests pass:
211
+
212
+ rake spec
213
+
214
+ Push to your fork and [submit a pull request]( https://github.com/cronitorio/cronitor-ruby/compare/)
215
+
216
+
217
+ ## Release a new version
218
+
219
+ The bump gem makes this easy:
220
+
221
+ 1. `rake bump:(major|minor|patch|pre)`
222
+ 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
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'cronitor'
data/cronitor.gemspec CHANGED
@@ -1,15 +1,19 @@
1
- lib = File.expand_path('../lib', __FILE__)
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/evertrue/cronitor'
14
+ spec.homepage = 'https://github.com/cronitorio/cronitor-ruby'
15
+
16
+ spec.required_ruby_version = '>= 2.5'
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.add_dependency 'unirest', '~> 1.1'
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,110 +1,87 @@
1
- require 'cronitor/version'
2
- require 'cronitor/error'
3
- require 'net/http'
4
- require 'unirest'
5
- require 'hashie'
6
-
7
- class Cronitor
8
- attr_accessor :token, :opts, :code
9
- API_URL = 'https://cronitor.io/v1'.freeze
10
- PING_URL = 'https://cronitor.link'.freeze
1
+ # frozen_string_literal: true
11
2
 
12
- def initialize(token: nil, opts: {}, code: nil)
13
- @token = token
14
- @opts = opts
15
- @code = code
3
+ require 'logger'
4
+ require 'json'
5
+ require 'httparty'
6
+ require 'socket'
7
+ require 'time'
8
+ require 'yaml'
16
9
 
17
- if @token.nil? && @code.nil?
18
- raise(
19
- Cronitor::Error,
20
- 'Either a Cronitor API token or an existing monitor code must be ' \
21
- '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')"
22
22
  )
23
23
  end
24
24
 
25
- if @opts
26
- Hashie.symbolize_keys! @opts
27
- exists? @opts[:name] if @opts.key? :name
28
- 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)
29
28
  end
30
29
 
31
- create if @code.nil?
32
- end
33
-
34
- def create
35
- response = Unirest.post(
36
- "#{API_URL}/monitors",
37
- headers: default_headers.merge('Content-Type' => 'application/json'),
38
- auth: { user: token },
39
- parameters: opts.to_json
40
- )
41
-
42
- @code = response.body['code'] if valid? response
43
- end
44
-
45
- def exists?(name)
46
- response = Unirest.get(
47
- "#{API_URL}/monitors/#{URI.escape(name).gsub('[', '%5B').gsub(']', '%5D')}",
48
- headers: default_headers,
49
- auth: { user: token }
50
- )
51
- return false unless response.code == 200
52
-
53
- @code = response.body['code']
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]
54
33
 
55
- true
56
- end
34
+ return unless output
57
35
 
58
- def ping(type, msg = nil)
59
- url = "#{PING_URL}/#{code}/#{type}"
60
- url += "?msg=#{URI.escape msg}" if %w(run complete fail).include?(type) && !msg.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
61
41
 
62
- response = Unirest.get url
63
- valid? response
64
- end
42
+ unless to_parse.is_a?(Hash)
43
+ raise ConfigurationError.new('A Hash with keys corresponding to monitor keys is expected.')
44
+ end
65
45
 
66
- def human_readable(rules)
67
- rules.each do |rule|
68
- unless rule[:human_readable]
69
- rule[:human_readable] = "#{rule[:rule_type]} #{rule[:duration]} " \
70
- "#{rule[:time_unit]}"
46
+ to_parse.each do |key, m|
47
+ m['key'] = key
48
+ m['type'] = t
49
+ monitors << m
71
50
  end
72
51
  end
52
+ conf['monitors'] = monitors
53
+ conf
73
54
  end
74
55
 
75
- private
76
-
77
- def valid?(response)
78
- return true if [200, 201].include? response.code
79
- server_error? response
80
-
81
- raise Cronitor::Error, error_msg(response.body)
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)
82
62
  end
83
63
 
84
- def error_msg(body, msg = [])
85
- body.each do |opt, value|
86
- if value.respond_to? 'each'
87
- value.each do |error_msg|
88
- msg << "#{opt}: #{error_msg}"
89
- end
90
- else
91
- msg << "#{opt}: #{value}"
92
- end
93
- end
94
-
95
- msg.join ' '
64
+ def self.validate_config
65
+ apply_config(rollback: true)
96
66
  end
97
67
 
98
- def server_error?(response)
99
- return unless [301, 302, 404, 500, 502, 503, 504].include? response.code
100
-
101
- raise(
102
- Cronitor::Error,
103
- "Something else has gone awry. HTTP status: #{response.code}"
104
- )
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
79
+ end
105
80
  end
106
81
 
107
- def default_headers
108
- { 'Accept' => 'application/json' }
82
+ def self.monitor_api_url
83
+ 'https://cronitor.io/api/monitors'
109
84
  end
110
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_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,4 +1,12 @@
1
- class Cronitor
1
+ # frozen_string_literal: true
2
+
3
+ module Cronitor
2
4
  class Error < StandardError
3
5
  end
6
+
7
+ class ValidationError < Error
8
+ end
9
+
10
+ class ConfigurationError < Error
11
+ end
4
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: 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
@@ -1,3 +1,5 @@
1
- class Cronitor
2
- VERSION = '1.1.5'
1
+ # frozen_string_literal: true
2
+
3
+ module Cronitor
4
+ VERSION = '4.1.0'
3
5
  end
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: 1.1.5
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Byrnes
8
- autorequire:
8
+ - August Flanagan
9
+ autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2017-07-19 00:00:00.000000000 Z
12
+ date: 2021-05-20 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- name: unirest
15
+ name: httparty
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - "~>"
18
+ - - ">="
18
19
  - !ruby/object:Gem::Version
19
- version: '1.1'
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: '1.1'
27
+ version: '0'
27
28
  - !ruby/object:Gem::Dependency
28
- name: hashie
29
+ name: bump
29
30
  requirement: !ruby/object:Gem::Requirement
30
31
  requirements:
31
32
  - - "~>"
32
33
  - !ruby/object:Gem::Version
33
- version: '3.4'
34
- type: :runtime
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: '3.4'
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: '1.10'
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: '1.10'
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: '10.0'
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: '10.0'
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: pry
99
+ name: rubocop
85
100
  requirement: !ruby/object:Gem::Requirement
86
101
  requirements:
87
102
  - - "~>"
88
103
  - !ruby/object:Gem::Version
89
- version: '0.10'
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: '0.10'
111
+ version: '1.8'
97
112
  - !ruby/object:Gem::Dependency
98
- name: webmock
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.21'
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.21'
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: '1.4'
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: '1.4'
153
+ version: '2.0'
125
154
  - !ruby/object:Gem::Dependency
126
- name: bump
155
+ name: webmock
127
156
  requirement: !ruby/object:Gem::Requirement
128
157
  requirements:
129
158
  - - "~>"
130
159
  - !ruby/object:Gem::Version
131
- version: '0.1'
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: '0.1'
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
- - ".travis.yml"
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/evertrue/cronitor
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: '0'
205
+ version: '2.5'
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
- rubyforge_project:
179
- rubygems_version: 2.6.6
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
@@ -1,4 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2
4
- before_install: gem install bundler -v 1.10