cronitor 2.0.0 → 4.1.1

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: 67371ddbb668fc2c736994aadb6b1610bd807aa4
4
- data.tar.gz: 22381ccb4ee4157fe32c3d518642976aa58a63d9
2
+ SHA256:
3
+ metadata.gz: 70d39a60f81039830a50c36c9885a6021155dfdef29403b2b28335f8be8fd2f8
4
+ data.tar.gz: 26d1637753bfbea371b1dd5add7752cb36b9a2a20d0c5f2a66c760813fde83b2
5
5
  SHA512:
6
- metadata.gz: fde7ed9489dd3b82435766ed0013dcef88fb2fe7f67ecca2e3cd7d954c3fff2c164df8e5af2a9b59343b5587c5d287df4a229968d1b4d306ea916605b81a0329
7
- data.tar.gz: 0e342c25712223f1b1f8e67c11b606bf75369a494c00f0bd3aacb6bc0c87272b8b6b44f5b2e0f81d5613779eabdaf95289716a0e2d639f20ba2e0453c252cad3
6
+ metadata.gz: 4ad94427bb731459e4a41463b0435a2afc6e904ad15a76a57d50557d77285931bca7773b537b9eb5433b382364ab0161e87f03b326f5dca5faa4997e70f8c63d
7
+ data.tar.gz: beb4775ed796685a6d1c90bb88c692cf8d2a34f834c858449138afffbcf53d258f84ff791c265d89e05db287d8259a9a7087f32c660410f99b40d965f49a0e19
@@ -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`, `heartbeats`. 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,11 +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_development_dependency 'bundler', '~> 1.10'
22
- spec.add_development_dependency 'rake', '~> 10.0'
23
- spec.add_development_dependency 'rspec', '~> 3.3'
25
+ spec.add_runtime_dependency 'httparty'
26
+
27
+ spec.add_development_dependency 'bump', '~> 0.1'
28
+ spec.add_development_dependency 'bundler'
24
29
  spec.add_development_dependency 'pry', '~> 0.10'
25
- spec.add_development_dependency 'webmock', '~> 3.1'
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'
26
35
  spec.add_development_dependency 'sinatra', '~> 2.0'
27
- spec.add_development_dependency 'bump', '~> 0.1'
36
+ spec.add_development_dependency 'webmock', '~> 3.1'
28
37
  end
data/lib/cronitor.rb CHANGED
@@ -1,130 +1,87 @@
1
- require 'cronitor/version'
2
- require 'cronitor/error'
3
- require 'json'
4
- require 'net/http'
5
- require 'uri'
1
+ # frozen_string_literal: true
6
2
 
7
- class Cronitor
8
- attr_accessor :token, :opts, :code
9
- API_URL = 'https://cronitor.io/v1'.freeze
10
- PING_URL = 'https://cronitor.link'.freeze
11
-
12
- def initialize(token: ENV['CRONITOR_TOKEN'], 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
- @opts = symbolize_keys @opts
27
- exists? @opts[:name] if @opts.key? :name
28
- human_readable @opts[:rules] if @opts.key? :rules
29
- end
30
-
31
- create if @code.nil?
32
- end
33
-
34
- def create
35
- uri = URI.parse "#{API_URL}/monitors"
36
-
37
- http = Net::HTTP.new uri.host, uri.port
38
- http.use_ssl = uri.scheme == 'https'
39
-
40
- request = Net::HTTP::Post.new uri.path, default_headers
41
- request.basic_auth token, nil
42
- request.content_type = 'application/json'
43
- request.body = JSON.generate opts
44
-
45
- response = http.request request
46
-
47
- @code = JSON.parse(response.body).fetch 'code' if valid? response
48
- end
49
-
50
- def exists?(name)
51
- uri = URI.parse "#{API_URL}/monitors/#{URI.escape name}"
52
-
53
- http = Net::HTTP.new uri.host, uri.port
54
- http.use_ssl = uri.scheme == 'https'
55
-
56
- request = Net::HTTP::Get.new uri.path, default_headers
57
- request.basic_auth token, nil
58
-
59
- response = http.request request
60
-
61
- return false unless response.is_a? Net::HTTPSuccess
62
-
63
- @code = JSON.parse(response.body).fetch 'code'
64
-
65
- true
66
- end
67
-
68
- def ping(type, msg = nil)
69
- uri = URI.parse "#{PING_URL}/#{URI.escape code}/#{URI.escape type}"
70
- if %w[run complete fail].include?(type) && !msg.nil?
71
- uri.query = URI.encode_www_form 'msg' => msg
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)
72
28
  end
73
29
 
74
- http = Net::HTTP.new uri.host, uri.port
75
- http.use_ssl = uri.scheme == 'https'
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]
76
33
 
77
- request = Net::HTTP::Get.new uri, default_headers
34
+ return unless output
78
35
 
79
- response = http.request request
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
80
41
 
81
- valid? response
82
- end
42
+ unless to_parse.is_a?(Hash)
43
+ raise ConfigurationError.new('A Hash with keys corresponding to monitor keys is expected.')
44
+ end
83
45
 
84
- def human_readable(rules)
85
- rules.each do |rule|
86
- unless rule[:human_readable]
87
- rule[:human_readable] = "#{rule[:rule_type]} #{rule[:duration]} " \
88
- "#{rule[:time_unit]}"
46
+ to_parse.each do |key, m|
47
+ m['key'] = key
48
+ m['type'] = t
49
+ monitors << m
89
50
  end
90
51
  end
52
+ conf['monitors'] = monitors
53
+ conf
91
54
  end
92
55
 
93
- private
94
-
95
- def valid?(response)
96
- return true if response.is_a? Net::HTTPSuccess
97
-
98
- msg = if response.content_type.match? 'json'
99
- error_msg JSON.parse(response.body)
100
- else
101
- "Something else has gone awry. HTTP status: #{response.code}"
102
- end
103
-
104
- raise Cronitor::Error, msg
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)
105
62
  end
106
63
 
107
- def error_msg(body, msg = [])
108
- body.each do |opt, value|
109
- if value.respond_to? 'each'
110
- value.each do |error_msg|
111
- msg << "#{opt}: #{error_msg}"
112
- end
113
- else
114
- msg << "#{opt}: #{value}"
115
- end
116
- end
117
-
118
- msg.join ' '
64
+ def self.validate_config
65
+ apply_config(rollback: true)
119
66
  end
120
67
 
121
- def default_headers
122
- { 'Accept' => 'application/json' }
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
123
80
  end
124
81
 
125
- def symbolize_keys(hash)
126
- hash.each_with_object({}) do |(k, v), h|
127
- h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
128
- end
82
+ def self.monitor_api_url
83
+ 'https://cronitor.io/api/monitors'
129
84
  end
130
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: 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 = '2.0.0'.freeze
1
+ # frozen_string_literal: true
2
+
3
+ module Cronitor
4
+ VERSION = '4.1.1'
3
5
  end
metadata CHANGED
@@ -1,43 +1,86 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cronitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 4.1.1
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-11-30 00:00:00.000000000 Z
12
+ date: 2021-05-28 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- name: bundler
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'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bump
15
30
  requirement: !ruby/object:Gem::Requirement
16
31
  requirements:
17
32
  - - "~>"
18
33
  - !ruby/object:Gem::Version
19
- version: '1.10'
34
+ version: '0.1'
20
35
  type: :development
21
36
  prerelease: false
22
37
  version_requirements: !ruby/object:Gem::Requirement
23
38
  requirements:
24
39
  - - "~>"
25
40
  - !ruby/object:Gem::Version
26
- version: '1.10'
41
+ version: '0.1'
27
42
  - !ruby/object:Gem::Dependency
28
- name: rake
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
29
58
  requirement: !ruby/object:Gem::Requirement
30
59
  requirements:
31
60
  - - "~>"
32
61
  - !ruby/object:Gem::Version
33
- version: '10.0'
62
+ version: '0.10'
34
63
  type: :development
35
64
  prerelease: false
36
65
  version_requirements: !ruby/object:Gem::Requirement
37
66
  requirements:
38
67
  - - "~>"
39
68
  - !ruby/object:Gem::Version
40
- version: '10.0'
69
+ version: '0.10'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
41
84
  - !ruby/object:Gem::Dependency
42
85
  name: rspec
43
86
  requirement: !ruby/object:Gem::Requirement
@@ -53,33 +96,47 @@ dependencies:
53
96
  - !ruby/object:Gem::Version
54
97
  version: '3.3'
55
98
  - !ruby/object:Gem::Dependency
56
- name: pry
99
+ name: rubocop
57
100
  requirement: !ruby/object:Gem::Requirement
58
101
  requirements:
59
102
  - - "~>"
60
103
  - !ruby/object:Gem::Version
61
- version: '0.10'
104
+ version: '1.8'
62
105
  type: :development
63
106
  prerelease: false
64
107
  version_requirements: !ruby/object:Gem::Requirement
65
108
  requirements:
66
109
  - - "~>"
67
110
  - !ruby/object:Gem::Version
68
- version: '0.10'
111
+ version: '1.8'
69
112
  - !ruby/object:Gem::Dependency
70
- name: webmock
113
+ name: rubocop-rake
71
114
  requirement: !ruby/object:Gem::Requirement
72
115
  requirements:
73
116
  - - "~>"
74
117
  - !ruby/object:Gem::Version
75
- version: '3.1'
118
+ version: 0.5.1
76
119
  type: :development
77
120
  prerelease: false
78
121
  version_requirements: !ruby/object:Gem::Requirement
79
122
  requirements:
80
123
  - - "~>"
81
124
  - !ruby/object:Gem::Version
82
- version: '3.1'
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
@@ -95,30 +152,33 @@ dependencies:
95
152
  - !ruby/object:Gem::Version
96
153
  version: '2.0'
97
154
  - !ruby/object:Gem::Dependency
98
- name: bump
155
+ name: webmock
99
156
  requirement: !ruby/object:Gem::Requirement
100
157
  requirements:
101
158
  - - "~>"
102
159
  - !ruby/object:Gem::Version
103
- version: '0.1'
160
+ version: '3.1'
104
161
  type: :development
105
162
  prerelease: false
106
163
  version_requirements: !ruby/object:Gem::Requirement
107
164
  requirements:
108
165
  - - "~>"
109
166
  - !ruby/object:Gem::Version
110
- version: '0.1'
111
- description:
167
+ version: '3.1'
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
- - ".travis.yml"
181
+ - ".rubocop.yml"
122
182
  - Gemfile
123
183
  - LICENSE.txt
124
184
  - README.md
@@ -127,12 +187,14 @@ files:
127
187
  - bin/setup
128
188
  - cronitor.gemspec
129
189
  - lib/cronitor.rb
190
+ - lib/cronitor/config.rb
130
191
  - lib/cronitor/error.rb
192
+ - lib/cronitor/monitor.rb
131
193
  - lib/cronitor/version.rb
132
- homepage: https://github.com/evertrue/cronitor
194
+ homepage: https://github.com/cronitorio/cronitor-ruby
133
195
  licenses: []
134
196
  metadata: {}
135
- post_install_message:
197
+ post_install_message:
136
198
  rdoc_options: []
137
199
  require_paths:
138
200
  - lib
@@ -140,16 +202,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
202
  requirements:
141
203
  - - ">="
142
204
  - !ruby/object:Gem::Version
143
- version: '0'
205
+ version: '2.5'
144
206
  required_rubygems_version: !ruby/object:Gem::Requirement
145
207
  requirements:
146
208
  - - ">="
147
209
  - !ruby/object:Gem::Version
148
210
  version: '0'
149
211
  requirements: []
150
- rubyforge_project:
151
- rubygems_version: 2.6.13
152
- signing_key:
212
+ rubygems_version: 3.1.4
213
+ signing_key:
153
214
  specification_version: 4
154
215
  summary: An interface for the Cronitor API
155
216
  test_files: []
data/.travis.yml DELETED
@@ -1,3 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.4