xronor 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +13 -0
- data/Guardfile +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/README.md +3 -0
- data/docs/dsl.md +179 -0
- data/exe/xronor +5 -0
- data/lib/xronor/aws/cloud_watch_events.rb +70 -0
- data/lib/xronor/aws/dynamo_db.rb +44 -0
- data/lib/xronor/aws/lambda.rb +14 -0
- data/lib/xronor/cli.rb +43 -0
- data/lib/xronor/core_ext/numeric.rb +13 -0
- data/lib/xronor/dsl/checker.rb +25 -0
- data/lib/xronor/dsl/default.rb +27 -0
- data/lib/xronor/dsl/job.rb +66 -0
- data/lib/xronor/dsl/numeric_seconds.rb +75 -0
- data/lib/xronor/dsl/schedule_converter.rb +147 -0
- data/lib/xronor/dsl.rb +63 -0
- data/lib/xronor/generator/cloud_watch_events.rb +77 -0
- data/lib/xronor/generator/crontab.rb +18 -0
- data/lib/xronor/generator/erb.rb +24 -0
- data/lib/xronor/job.rb +44 -0
- data/lib/xronor/parser.rb +13 -0
- data/lib/xronor/version.rb +3 -0
- data/lib/xronor.rb +30 -0
- data/xronor.gemspec +32 -0
- metadata +177 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b9da6e45fffe237ba7dc775a577a5b13aaccf6ed
|
4
|
+
data.tar.gz: 1fb3ef2dfdd321ee7462ca6c9120ee749d9e8e88
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dbe0036b34de528d5cccbd770243333eb9bf91ae8441f88023eea7611636680ef273c75e96df45624e443e1f0c70fa3981c09291cdfbe04f697cb036d3f700e2
|
7
|
+
data.tar.gz: 062ca0625de294f93a11ffc572a09d0be09e50e98fa43521157f37625a137add6819c653243a9249cb8ae394e82a5c7c74dde8b7e6953277494226414b65617b
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in xronor.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
gem "guard", "~> 2.14", require: false
|
8
|
+
gem "guard-rspec", "~> 4.7", require: false
|
9
|
+
end
|
10
|
+
|
11
|
+
group :test do
|
12
|
+
gem "codecov", "~> 0.1", require: false
|
13
|
+
end
|
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/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Daisuke Fujita
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# Xronor
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/dtan4/xronor.svg?branch=master)](https://travis-ci.org/dtan4/xronor)
|
4
|
+
[![codecov](https://codecov.io/gh/dtan4/xronor/branch/master/graph/badge.svg)](https://codecov.io/gh/dtan4/xronor)
|
5
|
+
|
6
|
+
Timezone-aware Job Scheduler DSL and Converter
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
job_template "/bin/bash -l -c ':job'"
|
10
|
+
|
11
|
+
job_type :rake, "bundle exec rake :task RAILS_ENV=production"
|
12
|
+
|
13
|
+
default do
|
14
|
+
timezone "Asia/Tokyo" # UTC+9
|
15
|
+
end
|
16
|
+
|
17
|
+
every 1.hour, at: 15 do
|
18
|
+
name "Send awesome mails"
|
19
|
+
rake "send_awesome_mail"
|
20
|
+
end
|
21
|
+
|
22
|
+
every :day, at: '0:00 am' do
|
23
|
+
name "Send greeting notifications"
|
24
|
+
description "Send greeting notifications for all users"
|
25
|
+
rake "send_greeting_notification"
|
26
|
+
end
|
27
|
+
|
28
|
+
every :day, at: '0:00 am', timezone: "Europe/Berlin" do # UTC+1
|
29
|
+
name "Send notifications for Berlin"
|
30
|
+
description "Send notifications for Berlin"
|
31
|
+
rake "send_notification[Europe/Berlin]"
|
32
|
+
end
|
33
|
+
|
34
|
+
every :wednesday, at: '0:10 am' do
|
35
|
+
name "Create new companies"
|
36
|
+
rake "create_new_companies"
|
37
|
+
end
|
38
|
+
|
39
|
+
every "0 10 10,20 * *" do
|
40
|
+
name "Healthcheck"
|
41
|
+
rake "ping"
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
## Table of contents
|
46
|
+
|
47
|
+
- [Why](#why)
|
48
|
+
* [Scheduled job execution system](#scheduled-job-execution-system)
|
49
|
+
* [Scheduler DSL](#scheduler-dsl)
|
50
|
+
* [:point_right:](#point_right)
|
51
|
+
- [Installation](#installation)
|
52
|
+
- [Usage](#usage)
|
53
|
+
- [Xronor DSL](#xronor-dsl)
|
54
|
+
- [Development](#development)
|
55
|
+
- [Contributing](#contributing)
|
56
|
+
- [Author](#author)
|
57
|
+
- [License](#license)
|
58
|
+
|
59
|
+
## Why
|
60
|
+
|
61
|
+
### Scheduled job execution system
|
62
|
+
|
63
|
+
As you know, Cron is commonly used for scheduled jobs.
|
64
|
+
However, Cron has some difficulties:
|
65
|
+
|
66
|
+
- Does not consider timezone. It depends on machine environment where cron daemon runs.
|
67
|
+
- Does not contain job metadata (name, description, ...).
|
68
|
+
- Cron daemon cannot be distributed. Machine where cron daemon runs can be SPOF.
|
69
|
+
|
70
|
+
Recently there are solutions for the last point, e.g. [Azure Scheduler](https://azure.microsoft.com/en-us/services/scheduler/), [CloudWatch Events](http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/WhatIsCloudWatchEvents.html) and [Kubernetes Cron Job](https://kubernetes.io/docs/user-guide/cron-jobs/), but those services still have fixed timezone to UTC.
|
71
|
+
|
72
|
+
### Scheduler DSL
|
73
|
+
|
74
|
+
[Whenever gem](https://github.com/javan/whenever) is very useful to describe scheduled jobs in human-friendly format.
|
75
|
+
However, Whenever cannot treat timezone and metadata so that it is just a wrapper of Cron expression.
|
76
|
+
|
77
|
+
### :point_right:
|
78
|
+
|
79
|
+
To resolve above problems, we need:
|
80
|
+
|
81
|
+
- _timezone-aware_ job scheduler DSL
|
82
|
+
- Just like an enhance of Whenever
|
83
|
+
- a DSL converter which is easy to register CloudWatch Events rule
|
84
|
+
|
85
|
+
|
86
|
+
## Installation
|
87
|
+
|
88
|
+
Add this line to your application's Gemfile:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
gem 'xronor'
|
92
|
+
```
|
93
|
+
|
94
|
+
And then execute:
|
95
|
+
|
96
|
+
$ bundle
|
97
|
+
|
98
|
+
Or install it yourself as:
|
99
|
+
|
100
|
+
$ gem install xronor
|
101
|
+
|
102
|
+
## Usage
|
103
|
+
|
104
|
+
```
|
105
|
+
Commands:
|
106
|
+
xronor crontab SCHEDULEFILE
|
107
|
+
xronor cwa SCHEDULEFILE --cluster=CLUSTER --container=CONTAINER --function=FUNCTION --table=TABLE --task-definition=TASK_DEFINITION
|
108
|
+
xronor template SCHEDULEFILE --template=TEMPLATE
|
109
|
+
xronor template_per_job SCHEDULERFILE --outdir=OUTDIR --template=TEMPLATE
|
110
|
+
```
|
111
|
+
|
112
|
+
Xronor CLI converts DSL file to:
|
113
|
+
|
114
|
+
- `xronor cwa` CloudWatch Events Rule (requires `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION` environment variables)
|
115
|
+
- `xronor crontab` crontab file
|
116
|
+
- file(s) from ERB template
|
117
|
+
- `xronor template` write all jobs in one file
|
118
|
+
- `xronor template_per_job` generate files per job
|
119
|
+
|
120
|
+
## Xronor DSL
|
121
|
+
|
122
|
+
:point_right: [docs/dsl](docs/dsl.md)
|
123
|
+
|
124
|
+
## Development
|
125
|
+
|
126
|
+
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.
|
127
|
+
|
128
|
+
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).
|
129
|
+
|
130
|
+
## Contributing
|
131
|
+
|
132
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dtan4/xronor.
|
133
|
+
|
134
|
+
## Author
|
135
|
+
|
136
|
+
Daisuke Fujita ([@dtan4](https://github.com/dtan4))
|
137
|
+
|
138
|
+
## License
|
139
|
+
|
140
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "xronor"
|
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/setup
ADDED
data/docs/README.md
ADDED
data/docs/dsl.md
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
# Xronor DSL
|
2
|
+
|
3
|
+
Xronor DSL is heavily inspired by [Whenever](https://github.com/javan/whenever) DSL.
|
4
|
+
|
5
|
+
For example,
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
job_template "/bin/bash -l -c ':job'"
|
9
|
+
|
10
|
+
job_type :rake, "bundle exec rake :task RAILS_ENV=production"
|
11
|
+
|
12
|
+
default do
|
13
|
+
timezone "Asia/Tokyo" # UTC+9
|
14
|
+
end
|
15
|
+
|
16
|
+
every 1.hour, at: 15 do
|
17
|
+
name "Send awesome mails"
|
18
|
+
rake "send_awesome_mail"
|
19
|
+
end
|
20
|
+
|
21
|
+
every :day, at: '0:00 am' do
|
22
|
+
name "Send greeting notifications"
|
23
|
+
description "Send greeting notifications for all users"
|
24
|
+
rake "send_greeting_notification"
|
25
|
+
end
|
26
|
+
|
27
|
+
every :day, at: '0:00 am', timezone: "Europe/Berlin" do # UTC+1
|
28
|
+
name "Send notifications for Berlin"
|
29
|
+
description "Send notifications for Berlin"
|
30
|
+
rake "send_notification[Europe/Berlin]"
|
31
|
+
end
|
32
|
+
|
33
|
+
every :wednesday, at: '0:10 am' do
|
34
|
+
name "Create new companies"
|
35
|
+
rake "create_new_companies"
|
36
|
+
end
|
37
|
+
|
38
|
+
every "0 10 10,20 * *" do
|
39
|
+
name "Healthcheck"
|
40
|
+
rake "ping"
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
will be converted to the following crontab:
|
45
|
+
|
46
|
+
```
|
47
|
+
# Send awesome mails - Send awesome mails
|
48
|
+
15 * * * * /bin/bash -l -c 'bundle exec rake send_awesome_mail RAILS_ENV=production'
|
49
|
+
|
50
|
+
# Update Elasticsearch indices - Update Elasticsearch indices
|
51
|
+
10 * * * * /bin/bash -l -c 'bundle exec rake update_elasticsearch RAILS_ENV=production'
|
52
|
+
|
53
|
+
# Send greeting notifications - Send greeting notifications for all users
|
54
|
+
0 15 * * * /bin/bash -l -c 'bundle exec rake send_greeting_notification RAILS_ENV=production'
|
55
|
+
|
56
|
+
# Send notifications for Berlin - Send notifications for Berlin
|
57
|
+
0 23 * * * /bin/bash -l -c 'bundle exec rake send_notification[Europe/Berlin] RAILS_ENV=production'
|
58
|
+
|
59
|
+
# Create new companies - Create new companies
|
60
|
+
10 15 * * 2 /bin/bash -l -c 'bundle exec rake create_new_companies RAILS_ENV=production'
|
61
|
+
|
62
|
+
# Healthcheck - Healthcheck
|
63
|
+
0 10 10,20 * * /bin/bash -l -c 'bundle exec rake ping RAILS_ENV=production'
|
64
|
+
```
|
65
|
+
|
66
|
+
## Table of contents
|
67
|
+
|
68
|
+
- [Configuration](#configuration)
|
69
|
+
* [`job_template`](#job_template)
|
70
|
+
* [`job_type` (Required at least one)](#job_type-required-at-least-one)
|
71
|
+
* [`default`](#default)
|
72
|
+
- [Job definition](#job-definition)
|
73
|
+
* [`every do ... end`](#every---do--end)
|
74
|
+
* [`name` (Required)](#name-required)
|
75
|
+
* [`description`](#description)
|
76
|
+
* [`job_type` (e.g. `rake`)](#job_type-eg-rake)
|
77
|
+
|
78
|
+
## Configuration
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
job_template "/bin/bash -l -c ':job'"
|
82
|
+
|
83
|
+
job_type :rake, "bundle exec rake :task RAILS_ENV=production"
|
84
|
+
|
85
|
+
default do
|
86
|
+
timezone "Asia/Tokyo" # UTC+9
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
### `job_template`
|
91
|
+
|
92
|
+
Define common job command template.
|
93
|
+
`:job` will be replaced to per-job command.
|
94
|
+
|
95
|
+
Default: `:job`
|
96
|
+
|
97
|
+
### `job_type` (Required at least one)
|
98
|
+
|
99
|
+
Define job type and per-job command template.
|
100
|
+
`:task` will be replaced to the first argument of job.
|
101
|
+
|
102
|
+
For example, the following configuration will generate `/bin/bash -l -c 'bundle exec rake update_elasticsearch'`.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
job_template "/bin/bash -l -c ':job'"
|
106
|
+
|
107
|
+
job_type :rake, "bundle exec rake :task"
|
108
|
+
|
109
|
+
every 1.minute do
|
110
|
+
rake "update_elasticsearch"
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
### `default`
|
115
|
+
|
116
|
+
Set default timezone.
|
117
|
+
Timezone format follows [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g. `UTC`, `Asia/Tokyo`).
|
118
|
+
|
119
|
+
|key|description|default|
|
120
|
+
|---|---|---|
|
121
|
+
|`timezone`|Timezone of described time in DSL|`UTC`|
|
122
|
+
|`cron_timezone`|Timezone of the machine where schedule engine runs|`UTC`|
|
123
|
+
|
124
|
+
For example, the following configuration will parse `10:30 am` as `10:30 am UTC+9` then convert to `30 1 * * *` (`1:30 am UTC`).
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
default do
|
128
|
+
timezone "Asia/Tokyo" # UTC+9
|
129
|
+
cron_timezone "UTC"
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
## Job definition
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
every :day, at: '0:00 am', timezone: "Europe/Berlin" do # UTC+1
|
137
|
+
name "Send notifications for Berlin"
|
138
|
+
description "Send notifications for Berlin"
|
139
|
+
rake "send_notification[Europe/Berlin]"
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
### `every <frequency> <options> do ... end`
|
144
|
+
|
145
|
+
Define job schedule.
|
146
|
+
|
147
|
+
Available `<frequency>`:
|
148
|
+
|
149
|
+
|key|description|
|
150
|
+
|---|---|
|
151
|
+
|`:minute`|Invoke at every minute|
|
152
|
+
|`:hour`|Invoke at every hour|
|
153
|
+
|`:day`|Invoke at every day|
|
154
|
+
|`:sunday`, `:monday`, ..., `:saturday`|Invoke at every weekday|
|
155
|
+
|`N.minutes` (N = 1,2,3,...)|Invoke at every N minutes|
|
156
|
+
|`N.hours` (N = 1,2,3,...)|Invoke at every N hours|
|
157
|
+
|`N.days` (N = 1,2,3,...)|Invoke at every N days|
|
158
|
+
|`0 10 10,20 * *`|Cron expression in `cron_timezone`|
|
159
|
+
|
160
|
+
Available `<options>`:
|
161
|
+
|
162
|
+
|key|description|
|
163
|
+
|---|---|
|
164
|
+
|`at`|Invocation time|
|
165
|
+
|`timezone`|Timezone of described time in DSL|
|
166
|
+
|`cron_timezone`|Timezone of the machine where schedule engine runs|
|
167
|
+
|
168
|
+
### `name` (Required)
|
169
|
+
|
170
|
+
Define job name
|
171
|
+
|
172
|
+
### `description`
|
173
|
+
|
174
|
+
Define job description.
|
175
|
+
If `description` is not specified, job name will be used as description.
|
176
|
+
|
177
|
+
### `job_type` (e.g. `rake`)
|
178
|
+
|
179
|
+
Define job command.
|
data/exe/xronor
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Xronor
|
2
|
+
module AWS
|
3
|
+
class CloudWatchEvents
|
4
|
+
def initialize(client: Aws::CloudWatchEvents::Client.new)
|
5
|
+
@client = client
|
6
|
+
end
|
7
|
+
|
8
|
+
def deregister_job(job_name)
|
9
|
+
targets = @client.list_targets_by_rule(rule: job_name).targets
|
10
|
+
@client.remove_targets(rule: job_name, ids: targets.map(&:id))
|
11
|
+
@client.delete_rule(name: job_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def list_jobs(prefix = "")
|
15
|
+
@client.list_rules.rules.select { |rule| rule.name.start_with?(prefix) }.map(&:name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def register_job(job, prefix, cluster, task_definition, container, target_function_arn)
|
19
|
+
rule_name = job.cloud_watch_rule_name(prefix)
|
20
|
+
rule_arn = put_rule(rule_name, job.description, job.cloud_watch_schedule)
|
21
|
+
put_target(rule_name, cluster, task_definition, container, job.command, target_function_arn)
|
22
|
+
rule_arn
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def put_rule(name, description, schedule)
|
28
|
+
@client.put_rule({
|
29
|
+
name: name,
|
30
|
+
description: description,
|
31
|
+
schedule_expression: schedule,
|
32
|
+
}).rule_arn
|
33
|
+
end
|
34
|
+
|
35
|
+
def put_target(name, cluster, task_definition, container, command, target_function_arn)
|
36
|
+
@client.put_targets({
|
37
|
+
rule: name,
|
38
|
+
targets: [
|
39
|
+
{
|
40
|
+
id: generate_id,
|
41
|
+
arn: target_function_arn,
|
42
|
+
input_transformer: {
|
43
|
+
input_paths_map: {
|
44
|
+
"resources" => "$.resources",
|
45
|
+
"time" => "$.time",
|
46
|
+
},
|
47
|
+
input_template: generate_input_template(cluster, task_definition, container, command),
|
48
|
+
},
|
49
|
+
},
|
50
|
+
],
|
51
|
+
})
|
52
|
+
end
|
53
|
+
|
54
|
+
def generate_input_template(cluster, task_definition, container, command)
|
55
|
+
JSON.generate({
|
56
|
+
"resources" => '###$.resources###',
|
57
|
+
"time" => '###$.time###',
|
58
|
+
"cluster" => cluster,
|
59
|
+
"task_definition" => task_definition,
|
60
|
+
"container" => container,
|
61
|
+
"command" => Shellwords.split(command),
|
62
|
+
}).sub('"###$.resources###"', "<resources>").sub('"###$.time###"', "<time>")
|
63
|
+
end
|
64
|
+
|
65
|
+
def generate_id
|
66
|
+
SecureRandom.uuid
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Xronor
|
2
|
+
module AWS
|
3
|
+
class DynamoDB
|
4
|
+
BATCH_WRITE_ITEM_MAX = 25
|
5
|
+
|
6
|
+
def initialize(client: Aws::DynamoDB::Client.new)
|
7
|
+
@client = client
|
8
|
+
end
|
9
|
+
|
10
|
+
def sync_rule_arns(table, add_rule_arns, delete_rule_arns)
|
11
|
+
put_requests = add_rule_arns.map do |arn|
|
12
|
+
{
|
13
|
+
put_request: {
|
14
|
+
item: {
|
15
|
+
"ARN" => arn,
|
16
|
+
"InvokedAt" => "0"
|
17
|
+
},
|
18
|
+
},
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
delete_requests = delete_rule_arns.map do |arn|
|
23
|
+
{
|
24
|
+
delete_request: {
|
25
|
+
key: {
|
26
|
+
"ARN" => arn,
|
27
|
+
},
|
28
|
+
},
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
requests = put_requests + delete_requests
|
33
|
+
|
34
|
+
requests.each_slice(BATCH_WRITE_ITEM_MAX) do |reqs|
|
35
|
+
@client.batch_write_item({
|
36
|
+
request_items: {
|
37
|
+
table => reqs,
|
38
|
+
},
|
39
|
+
})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Xronor
|
2
|
+
module AWS
|
3
|
+
class Lambda
|
4
|
+
def initialize(client: Aws::Lambda::Client.new)
|
5
|
+
@client = client
|
6
|
+
end
|
7
|
+
|
8
|
+
def retrieve_function_arn(name)
|
9
|
+
function = @client.list_functions.functions.find { |fn| fn.function_name == name }
|
10
|
+
function ? function.function_arn : ""
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/xronor/cli.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Xronor
|
2
|
+
class CLI < Thor
|
3
|
+
DEFAULT_JOB_PREFIX = "scheduler-"
|
4
|
+
|
5
|
+
desc "crontab SCHEDULEFILE", "Generate crontab file"
|
6
|
+
def crontab(filename)
|
7
|
+
body = Xronor::Generator::Crontab.generate(filename, options)
|
8
|
+
puts body
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "cwa SCHEDULEFILE", "Register CloudWatch Events - Scheduler & ECS job runner"
|
12
|
+
option :prefix, default: DEFAULT_JOB_PREFIX
|
13
|
+
option :function, required: true
|
14
|
+
option :cluster, required: true
|
15
|
+
option :task_definition, required: true
|
16
|
+
option :container, required: true
|
17
|
+
option :table, required: true
|
18
|
+
option :dry_run, type: :boolean, default: false
|
19
|
+
def cwa(filename)
|
20
|
+
Xronor::Generator::CloudWatchEvents.generate(filename, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "template SCHEDULEFILE", "Generate single file"
|
24
|
+
option :template, required: true
|
25
|
+
def template(filename)
|
26
|
+
body = Xronor::Generator::ERB.generate_all_in_one(filename, options)
|
27
|
+
puts body
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "template_per_job SCHEDULERFILE", "Generate files per job"
|
31
|
+
option :ext, default: nil
|
32
|
+
option :outdir, required: true
|
33
|
+
option :template, required: true
|
34
|
+
def template_per_job(filename)
|
35
|
+
contents = Xronor::Generator::ERB.generate_per_job(filename, options)
|
36
|
+
|
37
|
+
contents.each do |name, body|
|
38
|
+
path = File.join(options[:outdir], options[:ext] ? "#{name}.#{options[:ext]}" : name)
|
39
|
+
open(path, "w+") { |f| f.puts body }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Numeric
|
2
|
+
def respond_to?(method, include_private = false)
|
3
|
+
super || Xronor::DSL::NumericSeconds.public_method_defined?(method)
|
4
|
+
end
|
5
|
+
|
6
|
+
def method_missing(method, *args, &block)
|
7
|
+
if Xronor::DSL::NumericSeconds.public_method_defined?(method)
|
8
|
+
Xronor::DSL::NumericSeconds.new(self).send(method)
|
9
|
+
else
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Xronor
|
2
|
+
class DSL
|
3
|
+
module Checker
|
4
|
+
class ValidationError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
def required(name, value)
|
8
|
+
invalid = false
|
9
|
+
|
10
|
+
if value
|
11
|
+
case value
|
12
|
+
when String
|
13
|
+
invalid = value.strip.empty?
|
14
|
+
when Array, Hash
|
15
|
+
invalid = value.empty?
|
16
|
+
end
|
17
|
+
else
|
18
|
+
invalid = true
|
19
|
+
end
|
20
|
+
|
21
|
+
raise ValidationError.new("'#{name}' is required") if invalid
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|