cron_swanson 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +1 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/Gemfile +10 -0
- data/Guardfile +16 -0
- data/README.md +106 -0
- data/Rakefile +2 -0
- data/bin/_guard-core +29 -0
- data/bin/console +14 -0
- data/bin/guard +29 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/cron_swanson.gemspec +27 -0
- data/lib/cron_swanson.rb +63 -0
- data/lib/cron_swanson/version.rb +3 -0
- data/lib/cron_swanson/whenever.rb +51 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f5b92fde06c654c2a37b0162a87e0d1701db2d24f28ae707bbfa174d168d73aa
|
4
|
+
data.tar.gz: 33d169cfefdb9e1ab75aca3b93f2aedfc2f6c0d6634df1f9a536115b5a964bc9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: abd309004421dc9cf12886a4e9df6a58717ab51dc74c2cee0ab4fbdbbb4cb4248b462d4d684f6113a0103a8999559eac2cb51580467c6c5ed18b9ac14d73a1b6
|
7
|
+
data.tar.gz: af381bd1b0054755f8a08248962f546db9ce82ea0706657ace6f47fc5c9aa0425266de8295ee39bfe523bfc2b78e3d3243aa94c1eb8a49edd40ac355f7296557
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5.5
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
2
|
+
require "guard/rspec/dsl"
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
# Feel free to open issues for suggestions and improvements
|
6
|
+
|
7
|
+
# RSpec files
|
8
|
+
rspec = dsl.rspec
|
9
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
10
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
11
|
+
watch(rspec.spec_files)
|
12
|
+
|
13
|
+
# Ruby files
|
14
|
+
ruby = dsl.ruby
|
15
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
16
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# CronSwanson
|
2
|
+
|
3
|
+
CronSwanson can help if you have many systems running the same cron job, but you
|
4
|
+
don't want them all to start at exactly the same time. (To prevent daily load
|
5
|
+
spikes at midnight, as every single app starts its maintenance cron jobs.)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'cron_swanson'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install cron_swanson
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### schedule
|
26
|
+
|
27
|
+
Use `schedule` to build a cron schedule. The supplied string is hashed to determine
|
28
|
+
the run time of the job.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
CronSwanson.schedule 'whiskey'
|
32
|
+
#=> "33 18 * * *"
|
33
|
+
```
|
34
|
+
|
35
|
+
An `interval` (in seconds) can be supplied if you want a job to be run more than
|
36
|
+
once/day. This `interval` must be a factor of 24 hours.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
CronSwanson.schedule 'bacon', interval: 60 * 60 * 4
|
40
|
+
#=> "26 2,6,10,14,18,22 * * *"
|
41
|
+
```
|
42
|
+
|
43
|
+
### Whenever Integration
|
44
|
+
|
45
|
+
`CronSwanson` is built to integrate with the fantastic [whenever](https://github.com/javan/whenever) gem.
|
46
|
+
|
47
|
+
#### Daily
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# in the config/schedule.rb file
|
51
|
+
CronSwanson::Whenever.add(self) do
|
52
|
+
rake 'sample:job'
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
This will result in `rake sample:job` being scheduled once per day, at a time
|
57
|
+
determined by `CronSwanson`.
|
58
|
+
|
59
|
+
#### Multiple times/day
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# in the config/schedule.rb file
|
63
|
+
|
64
|
+
# with ActiveSupport
|
65
|
+
CronSwanson::Whenever.add(self, interval: 4.hours) do
|
66
|
+
rake 'sample:job'
|
67
|
+
end
|
68
|
+
|
69
|
+
# without ActiveSupport
|
70
|
+
CronSwanson::Whenever.add(self, interval: 60 * 60 * 4) do
|
71
|
+
rake 'sample:job'
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
#### job types
|
76
|
+
|
77
|
+
The block is evaluated by `whenever`, so any custom job types will work.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
# in config/schedule.rb
|
81
|
+
job_type :ron, '/usr/bin/ron :task'
|
82
|
+
|
83
|
+
CronSwanson::Whenever.add(self) do
|
84
|
+
ron 'bacon whiskey'
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
#### Limitation
|
89
|
+
|
90
|
+
The whenever integration code currently derives a scheduled time from the source
|
91
|
+
location of the `add` call. This means that moving the `.add` invocation to
|
92
|
+
a different line in schedule.rb will cause it to be run at a different time.
|
93
|
+
|
94
|
+
This limitation exists because I (currently) don't know of a way to inspect
|
95
|
+
the contents of a block at runtime. If a way to do this can be found, I
|
96
|
+
would prefer to calculate the time based on the block's contents.
|
97
|
+
|
98
|
+
## Development
|
99
|
+
|
100
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
101
|
+
|
102
|
+
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).
|
103
|
+
|
104
|
+
## Contributing
|
105
|
+
|
106
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/alexdean/cron_swanson.
|
data/Rakefile
ADDED
data/bin/_guard-core
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application '_guard-core' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("guard", "_guard-core")
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "cron_swanson"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/guard
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'guard' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("guard", "guard")
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/setup
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# lib = File.expand_path("../lib", __FILE__)
|
2
|
+
# $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
$:.push File.expand_path('../lib', __FILE__)
|
4
|
+
require "cron_swanson/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cron_swanson"
|
8
|
+
spec.version = CronSwanson::VERSION
|
9
|
+
spec.authors = ["Alex Dean"]
|
10
|
+
spec.email = ["github@mostlyalex.com"]
|
11
|
+
|
12
|
+
spec.summary = "distribute common cron jobs so they don't all start at the same moment"
|
13
|
+
spec.description = "easily schedule multiple apps to run the same job at different times"
|
14
|
+
spec.homepage = "https://github.com/alexdean/cron_swanson"
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.bindir = "exe"
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
end
|
data/lib/cron_swanson.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require "cron_swanson/version"
|
2
|
+
require "cron_swanson/whenever"
|
3
|
+
require 'digest/sha2'
|
4
|
+
|
5
|
+
# CronSwanson is a utility to generate run times for cron jobs which are
|
6
|
+
# regular, but which vary per job.
|
7
|
+
module CronSwanson
|
8
|
+
SECONDS_PER_HOUR = 60 * 60
|
9
|
+
SECONDS_PER_DAY = SECONDS_PER_HOUR * 24
|
10
|
+
|
11
|
+
def self.default_interval
|
12
|
+
SECONDS_PER_DAY
|
13
|
+
end
|
14
|
+
|
15
|
+
# offset within a time period
|
16
|
+
#
|
17
|
+
# if the interval is 6 hours, the returned offset will be some number of seconds
|
18
|
+
# from 0 to 6 hours.
|
19
|
+
#
|
20
|
+
# @param [String] job_identifier
|
21
|
+
# if nil, method will determine this on its own
|
22
|
+
# @param [Integer] interval how often will the job be run?
|
23
|
+
# @return [Integer] number of seconds to offset this job
|
24
|
+
def self.offset(job_identifier, interval: default_interval)
|
25
|
+
sha = Digest::SHA256.hexdigest(job_identifier.to_s)
|
26
|
+
|
27
|
+
# largest possible hex sha256 value
|
28
|
+
max_sha256_value = (16**64).to_f
|
29
|
+
|
30
|
+
# what % of the max sha256 is the current app?
|
31
|
+
sha_pct_of_max_sha256 = sha.to_i(16) / max_sha256_value
|
32
|
+
|
33
|
+
# apply that same % to the desired interval to get an offset
|
34
|
+
offset_seconds = (sha_pct_of_max_sha256 * interval).round
|
35
|
+
|
36
|
+
offset_seconds
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.schedule(job_identifier, interval: default_interval)
|
40
|
+
if interval > SECONDS_PER_DAY
|
41
|
+
raise ArgumentError, "interval must be less than 1 day (#{SECONDS_PER_DAY} seconds)."
|
42
|
+
end
|
43
|
+
|
44
|
+
if SECONDS_PER_DAY % interval != 0
|
45
|
+
raise ArgumentError, "A day (#{SECONDS_PER_DAY} seconds) must be evenly " \
|
46
|
+
"divisible by the given interval."
|
47
|
+
end
|
48
|
+
|
49
|
+
# figure out how many times job will happen in a day
|
50
|
+
runs_per_day = SECONDS_PER_DAY / interval
|
51
|
+
|
52
|
+
# raise if runs_per_day has a decimal component.
|
53
|
+
|
54
|
+
run_at = Time.at(offset(job_identifier, interval: interval)).utc
|
55
|
+
|
56
|
+
hours = []
|
57
|
+
runs_per_day.times do |i|
|
58
|
+
hours << run_at.hour + (i * interval / SECONDS_PER_HOUR)
|
59
|
+
end
|
60
|
+
|
61
|
+
"#{run_at.min} #{hours.join(',')} * * *"
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module CronSwanson
|
2
|
+
# integration for the whenever gem: https://github.com/javan/whenever
|
3
|
+
module Whenever
|
4
|
+
# CronSwanson integration for whenever
|
5
|
+
#
|
6
|
+
# The given block can use any job types understood by your whenever configuration.
|
7
|
+
# See https://github.com/javan/whenever#define-your-own-job-types.
|
8
|
+
#
|
9
|
+
# CronSwanson currently uses the location it is invoked from in schedule.rb
|
10
|
+
# to calculate a job time. This means that moving the `.add` invocation to
|
11
|
+
# a different line in schedule.rb will cause it to be run at a different time.
|
12
|
+
#
|
13
|
+
# This limitation exists because I (currently) don't know of a way to inspect
|
14
|
+
# the contents of a block at runtime. If a way to do this can be found, I
|
15
|
+
# would prefer to calculate the time based on the block's contents.
|
16
|
+
#
|
17
|
+
# @example run a job once/day
|
18
|
+
# # in the config/schedule.rb file
|
19
|
+
# CronSwanson::Whenever.add(self) do
|
20
|
+
# rake 'job'
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example run a job four times daily
|
24
|
+
# # in the config/schedule.rb file
|
25
|
+
#
|
26
|
+
# # with ActiveSupport
|
27
|
+
# CronSwanson::Whenever.add(self, interval: 4.hours) do
|
28
|
+
# rake 'job'
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # without ActiveSupport
|
32
|
+
# CronSwanson::Whenever.add(self, interval: 60 * 60 * 4) do
|
33
|
+
# rake 'job'
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @param [Whenever::JobList] whenever_job_list For code in `config/schedule.rb`
|
37
|
+
# this can be referred to as `self`.
|
38
|
+
# @param [Integer] interval how many seconds do you want between runs of this job
|
39
|
+
def self.add(whenever_job_list, interval: CronSwanson.default_interval, &block)
|
40
|
+
if !whenever_job_list.is_a?(::Whenever::JobList)
|
41
|
+
raise ArgumentError, "supply a Whenever::JobList. (In schedule.rb code, use `self`.)"
|
42
|
+
end
|
43
|
+
|
44
|
+
raise ArgumentError, "provide a block containing jobs to schedule." if !block_given?
|
45
|
+
|
46
|
+
# TODO: ideally we'd hash the contents of the block, not the location it was defined at
|
47
|
+
schedule = CronSwanson.schedule(block.source_location, interval: interval)
|
48
|
+
whenever_job_list.every(schedule, &Proc.new)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cron_swanson
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Dean
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.17'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.17'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: easily schedule multiple apps to run the same job at different times
|
42
|
+
email:
|
43
|
+
- github@mostlyalex.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".rspec"
|
50
|
+
- ".rubocop.yml"
|
51
|
+
- ".ruby-version"
|
52
|
+
- Gemfile
|
53
|
+
- Guardfile
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- bin/_guard-core
|
57
|
+
- bin/console
|
58
|
+
- bin/guard
|
59
|
+
- bin/rspec
|
60
|
+
- bin/setup
|
61
|
+
- cron_swanson.gemspec
|
62
|
+
- lib/cron_swanson.rb
|
63
|
+
- lib/cron_swanson/version.rb
|
64
|
+
- lib/cron_swanson/whenever.rb
|
65
|
+
homepage: https://github.com/alexdean/cron_swanson
|
66
|
+
licenses: []
|
67
|
+
metadata: {}
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.7.6.2
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: distribute common cron jobs so they don't all start at the same moment
|
88
|
+
test_files: []
|