xronor 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 +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
@@ -0,0 +1,27 @@
|
|
1
|
+
module Xronor
|
2
|
+
class DSL
|
3
|
+
class Default
|
4
|
+
DEFAULT_TIMEZONE = "UTC"
|
5
|
+
|
6
|
+
def initialize(&block)
|
7
|
+
@result = OpenStruct.new(
|
8
|
+
cron_timezone: DEFAULT_TIMEZONE,
|
9
|
+
prefix: "",
|
10
|
+
timezone: DEFAULT_TIMEZONE,
|
11
|
+
)
|
12
|
+
|
13
|
+
instance_eval(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
%i(cron_timezone prefix timezone).each do |key|
|
17
|
+
define_method(key) do |arg|
|
18
|
+
@result.send("#{key}=", arg)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def result
|
23
|
+
@result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Xronor
|
2
|
+
class DSL
|
3
|
+
class Job
|
4
|
+
include Xronor::DSL::Checker
|
5
|
+
|
6
|
+
def initialize(frequency, options, &block)
|
7
|
+
@frequency = frequency
|
8
|
+
@options = options
|
9
|
+
|
10
|
+
schedule = case frequency
|
11
|
+
when String # cron expression
|
12
|
+
frequency
|
13
|
+
when Symbol, Numeric # DSL (:hour, 1.min, 1.hour, ...)
|
14
|
+
Xronor::DSL::ScheduleConverter.convert(frequency, options)
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Invalid frequency #{frequency}"
|
17
|
+
end
|
18
|
+
|
19
|
+
@result = OpenStruct.new(
|
20
|
+
description: nil,
|
21
|
+
name: "",
|
22
|
+
schedule: schedule,
|
23
|
+
command: "",
|
24
|
+
)
|
25
|
+
|
26
|
+
instance_eval(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
%i(description name).each do |key|
|
30
|
+
define_method(key) do |arg|
|
31
|
+
@result.send("#{key}=", arg)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_template(template, options)
|
36
|
+
template.gsub(/:\w+/) do |key|
|
37
|
+
before_and_after = [$`[-1..-1], $'[0..0]]
|
38
|
+
option = options[key.sub(':', '').to_sym] || key
|
39
|
+
|
40
|
+
if before_and_after.all? { |c| c == "'" }
|
41
|
+
escape_single_quotes(option)
|
42
|
+
elsif before_and_after.all? { |c| c == '"' }
|
43
|
+
escape_double_quotes(option)
|
44
|
+
else
|
45
|
+
option
|
46
|
+
end
|
47
|
+
end.gsub(/\s+/m, " ").strip
|
48
|
+
end
|
49
|
+
|
50
|
+
def result
|
51
|
+
required(:name, @result.name)
|
52
|
+
@result
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def escape_single_quotes(str)
|
58
|
+
str.gsub(/'/) { "'\\''" }
|
59
|
+
end
|
60
|
+
|
61
|
+
def escape_double_quotes(str)
|
62
|
+
str.gsub(/"/) { '\"' }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# This file is derived from Whenever, Copyright (c) 2017 Javan Makhmali
|
2
|
+
# Original code:
|
3
|
+
# https://github.com/javan/whenever/blob/1dcb91484e6f1ee91c9272daccbe84111754102b/lib/whenever/numeric_seconds.rb
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person
|
6
|
+
# obtaining a copy of this software and associated documentation
|
7
|
+
# files (the "Software"), to deal in the Software without
|
8
|
+
# restriction, including without limitation the rights to use,
|
9
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the
|
11
|
+
# Software is furnished to do so, subject to the following
|
12
|
+
# conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
#
|
26
|
+
# Modifications are Copyright 2017 Daisuke Fujita. License under the MIT License.
|
27
|
+
|
28
|
+
module Xronor
|
29
|
+
class DSL
|
30
|
+
class NumericSeconds
|
31
|
+
def self.seconds(number, units)
|
32
|
+
self.new(number).send(units)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(number)
|
36
|
+
@number = number.to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
def seconds
|
40
|
+
@number
|
41
|
+
end
|
42
|
+
alias :second :seconds
|
43
|
+
|
44
|
+
def minutes
|
45
|
+
@number * 60
|
46
|
+
end
|
47
|
+
alias :minute :minutes
|
48
|
+
|
49
|
+
def hours
|
50
|
+
@number * 60 * 60
|
51
|
+
end
|
52
|
+
alias :hour :hours
|
53
|
+
|
54
|
+
def days
|
55
|
+
@number * 60 * 60 * 24
|
56
|
+
end
|
57
|
+
alias :day :days
|
58
|
+
|
59
|
+
def weeks
|
60
|
+
@number * 60 * 60 * 24 * 7
|
61
|
+
end
|
62
|
+
alias :week :weeks
|
63
|
+
|
64
|
+
def months
|
65
|
+
@number * 60 * 60 * 24 * 30
|
66
|
+
end
|
67
|
+
alias :month :months
|
68
|
+
|
69
|
+
def years
|
70
|
+
@number * 60 * 60 * 24 * 365 + 60 * 60 * 6 # consider leap year
|
71
|
+
end
|
72
|
+
alias :year :years
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# Part of logic in this file is derived from Whenever, Copyright (c) 2017 Javan Makhmali
|
2
|
+
# Original code:
|
3
|
+
# https://github.com/javan/whenever/blob/1dcb91484e6f1ee91c9272daccbe84111754102b/lib/whenever/cron.rb
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person
|
6
|
+
# obtaining a copy of this software and associated documentation
|
7
|
+
# files (the "Software"), to deal in the Software without
|
8
|
+
# restriction, including without limitation the rights to use,
|
9
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the
|
11
|
+
# Software is furnished to do so, subject to the following
|
12
|
+
# conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
#
|
26
|
+
# Modifications are Copyright 2017 Daisuke Fujita. License under the MIT License.
|
27
|
+
|
28
|
+
module Xronor
|
29
|
+
class DSL
|
30
|
+
class ScheduleConverter
|
31
|
+
WEEKDAYS = %i(sunday monday tuesday wednesday thursday friday saturday)
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def convert(frequency, options)
|
35
|
+
self.new(frequency, options).convert
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(frequency, options)
|
40
|
+
@frequency = frequency
|
41
|
+
@options = options
|
42
|
+
end
|
43
|
+
|
44
|
+
def convert
|
45
|
+
cron_at, dow_diff = case @options[:at]
|
46
|
+
when String
|
47
|
+
parse_and_convert_time
|
48
|
+
when Numeric
|
49
|
+
[@options[:at], 0]
|
50
|
+
else
|
51
|
+
[0, 0]
|
52
|
+
end
|
53
|
+
|
54
|
+
if WEEKDAYS.include?(@frequency) # :sunday, :monday, ..., :saturday
|
55
|
+
return cron_weekly(cron_at, dow_diff)
|
56
|
+
end
|
57
|
+
|
58
|
+
shortcut = case @frequency
|
59
|
+
when Numeric
|
60
|
+
@frequency
|
61
|
+
when :minute
|
62
|
+
Xronor::DSL.seconds(1, :minute)
|
63
|
+
when :hour
|
64
|
+
Xronor::DSL.seconds(1, :hour)
|
65
|
+
when :day
|
66
|
+
Xronor::DSL.seconds(1, :day)
|
67
|
+
end
|
68
|
+
|
69
|
+
raise ArgumentError, "Invalid frequency #{@frequency}" if !shortcut.is_a?(Numeric)
|
70
|
+
|
71
|
+
cron(shortcut, cron_at)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def comma_separated_timing(frequency, max, start = 0)
|
77
|
+
return start if frequency.nil? || frequency == "" || frequency == 0
|
78
|
+
return "*" if frequency == 1
|
79
|
+
return frequency if frequency > (max * 0.5) .ceil
|
80
|
+
|
81
|
+
original_start = start
|
82
|
+
|
83
|
+
start += frequency unless (max + 1).modulo(frequency).zero? || start > 0
|
84
|
+
output = (start..max).step(frequency).to_a
|
85
|
+
|
86
|
+
max_occurances = (max.to_f / (frequency.to_f)).round
|
87
|
+
max_occurances += 1 if original_start.zero?
|
88
|
+
|
89
|
+
output[0, max_occurances].join(',')
|
90
|
+
end
|
91
|
+
|
92
|
+
def cron(shortcut, cron_at)
|
93
|
+
digits = ["*", "*", "*", "*", "*"]
|
94
|
+
|
95
|
+
case shortcut
|
96
|
+
when Xronor::DSL.seconds(0, :second)...Xronor::DSL.seconds(1, :minute)
|
97
|
+
raise ArgumentError, "Time must be in minutes or higher"
|
98
|
+
when Xronor::DSL.seconds(1, :minute)...Xronor::DSL.seconds(1, :hour)
|
99
|
+
min_frequency = shortcut / 60
|
100
|
+
digits[0] = comma_separated_timing(min_frequency, 59, cron_at.is_a?(Time) ? cron_at.min : 0)
|
101
|
+
when Xronor::DSL.seconds(1, :hour)...Xronor::DSL.seconds(1, :day)
|
102
|
+
hour_frequency = (shortcut / 60 / 60).round
|
103
|
+
digits[0] = cron_at.is_a?(Time) ? cron_at.min : cron_at
|
104
|
+
digits[1] = comma_separated_timing(hour_frequency, 23, cron_at.is_a?(Time) ? cron_at.hour : 0)
|
105
|
+
when Xronor::DSL.seconds(1, :day)...Xronor::DSL.seconds(1, :month)
|
106
|
+
day_frequency = (shortcut / 24 / 60 / 60).round
|
107
|
+
digits[0] = cron_at.is_a?(Time) ? cron_at.min : 0
|
108
|
+
digits[1] = cron_at.is_a?(Time) ? cron_at.hour : cron_at
|
109
|
+
digits[2] = comma_separated_timing(day_frequency, 31, 1)
|
110
|
+
end
|
111
|
+
|
112
|
+
digits.join(" ")
|
113
|
+
end
|
114
|
+
|
115
|
+
def cron_weekly(cron_at, dow_diff)
|
116
|
+
dow = WEEKDAYS.index(@frequency)
|
117
|
+
dow += dow_diff
|
118
|
+
|
119
|
+
case dow
|
120
|
+
when -1 # Sunday -> Saturday
|
121
|
+
dow = 6
|
122
|
+
when 7 # Saturday -> Sunday
|
123
|
+
dow = 0
|
124
|
+
end
|
125
|
+
|
126
|
+
[
|
127
|
+
cron_at.is_a?(Time) ? cron_at.min : "0",
|
128
|
+
cron_at.is_a?(Time) ? cron_at.hour : "0",
|
129
|
+
"*",
|
130
|
+
"*",
|
131
|
+
dow,
|
132
|
+
].join(" ")
|
133
|
+
end
|
134
|
+
|
135
|
+
def parse_and_convert_time
|
136
|
+
original_time_class = Chronic.time_class
|
137
|
+
Time.zone = @options[:timezone]
|
138
|
+
Chronic.time_class = Time.zone
|
139
|
+
local_at = Chronic.parse(@options[:at])
|
140
|
+
cron_at = local_at.in_time_zone(@options[:cron_timezone])
|
141
|
+
Chronic.time_class = original_time_class
|
142
|
+
|
143
|
+
return cron_at, (cron_at.wday - local_at.wday)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/lib/xronor/dsl.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Xronor
|
2
|
+
class DSL
|
3
|
+
DEFAULT_PREFIX = "scheduler-"
|
4
|
+
DEFAULT_TIMEZONE = "UTC"
|
5
|
+
DEFAULT_CRON_TIMEZONE = "UTC"
|
6
|
+
DEFAULT_JOB_TEMPLATE = ":job"
|
7
|
+
|
8
|
+
class DuplicatedError < StandardError; end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def eval(body)
|
12
|
+
self.new(body)
|
13
|
+
end
|
14
|
+
|
15
|
+
def seconds(number, units)
|
16
|
+
Xronor::DSL::NumericSeconds.seconds(number, units)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(body)
|
21
|
+
@result = OpenStruct.new(
|
22
|
+
jobs: {},
|
23
|
+
options: {
|
24
|
+
prefix: DEFAULT_PREFIX,
|
25
|
+
timezone: DEFAULT_TIMEZONE,
|
26
|
+
cron_timezone: DEFAULT_CRON_TIMEZONE,
|
27
|
+
job_template: DEFAULT_JOB_TEMPLATE,
|
28
|
+
},
|
29
|
+
)
|
30
|
+
|
31
|
+
instance_eval(body)
|
32
|
+
end
|
33
|
+
|
34
|
+
def default(&block)
|
35
|
+
@result.options.merge!(Xronor::DSL::Default.new(&block).result.to_h)
|
36
|
+
end
|
37
|
+
|
38
|
+
def every(frequency, options = {}, &block)
|
39
|
+
job = Xronor::DSL::Job.new(frequency, @result.options.merge(options), &block).result
|
40
|
+
raise Xronor::DSL::DuplicatedError, "Job \"#{job.name}\" already exists" if @result.jobs[job.name]
|
41
|
+
@result.jobs[job.name] = job
|
42
|
+
end
|
43
|
+
|
44
|
+
def job_template(template)
|
45
|
+
@result.options[:job_template] = template
|
46
|
+
end
|
47
|
+
|
48
|
+
def job_type(name, template)
|
49
|
+
Xronor::DSL::Job.class_eval do
|
50
|
+
define_method(name) do |task, *args|
|
51
|
+
options = { task: task }
|
52
|
+
options.merge!(args[0]) if args[0].is_a? Hash
|
53
|
+
job = process_template(template, options)
|
54
|
+
@result.command = process_template(@options[:job_template], options.merge({ job: job }))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def result
|
60
|
+
@result
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Xronor
|
2
|
+
module Generator
|
3
|
+
class CloudWatchEvents
|
4
|
+
class << self
|
5
|
+
def generate(filename, options)
|
6
|
+
jobs = Xronor::Parser.parse(filename)
|
7
|
+
function_arn = lambda.retrieve_function_arn(options[:function])
|
8
|
+
|
9
|
+
current_jobs = cwe.list_jobs(options[:prefix])
|
10
|
+
add_jobs, delete_jobs = compare_jobs(options[:prefix], current_jobs, jobs)
|
11
|
+
|
12
|
+
added_rule_arns = add_jobs.map do |job|
|
13
|
+
if options[:dry_run]
|
14
|
+
puts "[DRYRUN] #{job.name} will be registered to CloudWatch Events"
|
15
|
+
else
|
16
|
+
arn = cwe.register_job(
|
17
|
+
job,
|
18
|
+
options[:prefix],
|
19
|
+
options[:cluster],
|
20
|
+
options[:task_definition],
|
21
|
+
options[:container],
|
22
|
+
function_arn,
|
23
|
+
)
|
24
|
+
puts "Registered #{arn}"
|
25
|
+
arn
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if options[:dry_run]
|
30
|
+
else
|
31
|
+
dynamodb.sync_rule_arns(options[:table], added_rule_arns, [])
|
32
|
+
end
|
33
|
+
|
34
|
+
delete_jobs.each do |job|
|
35
|
+
if options[:dry_run]
|
36
|
+
puts "[DRYRUN] #{job} will be deregistered from CloudWatch Events"
|
37
|
+
else
|
38
|
+
cwe.deregister_job(job)
|
39
|
+
puts "Deregistered #{job}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def compare_jobs(prefix, current_jobs, next_jobs)
|
47
|
+
add_jobs, delete_jobs = [], []
|
48
|
+
next_job_names = next_jobs.map do |job|
|
49
|
+
job.cloud_watch_rule_name(prefix)
|
50
|
+
end
|
51
|
+
|
52
|
+
next_jobs.each do |job|
|
53
|
+
add_jobs << job unless current_jobs.include?(job.cloud_watch_rule_name(prefix))
|
54
|
+
end
|
55
|
+
|
56
|
+
current_jobs.each do |job|
|
57
|
+
delete_jobs << job unless next_job_names.include?(job)
|
58
|
+
end
|
59
|
+
|
60
|
+
return add_jobs, delete_jobs
|
61
|
+
end
|
62
|
+
|
63
|
+
def cwe
|
64
|
+
@cwe ||= Xronor::AWS::CloudWatchEvents.new
|
65
|
+
end
|
66
|
+
|
67
|
+
def dynamodb
|
68
|
+
@dynamodb ||= Xronor::AWS::DynamoDB.new
|
69
|
+
end
|
70
|
+
|
71
|
+
def lambda
|
72
|
+
@lambda ||= Xronor::AWS::Lambda.new
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Xronor
|
2
|
+
module Generator
|
3
|
+
class Crontab
|
4
|
+
class << self
|
5
|
+
def generate(filename, options)
|
6
|
+
jobs = Xronor::Parser.parse(filename)
|
7
|
+
|
8
|
+
jobs.map do |job|
|
9
|
+
<<-EOS
|
10
|
+
# #{job.name} - #{job.description}
|
11
|
+
#{[job.schedule, job.command].join(" ")}
|
12
|
+
EOS
|
13
|
+
end.join("\n")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Xronor
|
2
|
+
module Generator
|
3
|
+
class ERB
|
4
|
+
class << self
|
5
|
+
def generate_all_in_one(filename, options)
|
6
|
+
@jobs = Xronor::Parser.parse(filename)
|
7
|
+
erb = open(options[:template]).read
|
8
|
+
::ERB.new(erb, nil, "-").result(binding)
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate_per_job(filename, options)
|
12
|
+
jobs = Xronor::Parser.parse(filename)
|
13
|
+
erb = open(options[:template]).read
|
14
|
+
|
15
|
+
jobs.inject({}) do |result, job|
|
16
|
+
@job = job
|
17
|
+
result[job.name.gsub(/[^\.\-_A-Za-z0-9]/, "_").downcase] = ::ERB.new(erb, nil, "-").result(binding)
|
18
|
+
result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/xronor/job.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Xronor
|
2
|
+
class Job
|
3
|
+
DOM_INDEX = 2
|
4
|
+
DOW_INDEX = 4
|
5
|
+
|
6
|
+
def initialize(name, description, schedule, command)
|
7
|
+
@name = name
|
8
|
+
@description = description
|
9
|
+
@schedule = schedule
|
10
|
+
@command = command
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :command, :description, :name, :schedule
|
14
|
+
|
15
|
+
def cloud_watch_schedule
|
16
|
+
cron_fields = @schedule.split(" ")
|
17
|
+
|
18
|
+
if cron_fields[DOM_INDEX] == "*" && cron_fields[DOW_INDEX] == "*"
|
19
|
+
cron_fields[DOW_INDEX] = "?"
|
20
|
+
else
|
21
|
+
cron_fields[DOM_INDEX] = "?" if cron_fields[DOM_INDEX] == "*"
|
22
|
+
|
23
|
+
if cron_fields[DOW_INDEX] == "*"
|
24
|
+
cron_fields[DOW_INDEX] = "?"
|
25
|
+
else
|
26
|
+
cron_fields[DOW_INDEX] = cron_fields[DOW_INDEX].to_i + 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
cron_fields << "*" # Year
|
31
|
+
"cron(#{cron_fields.join(" ")})"
|
32
|
+
end
|
33
|
+
|
34
|
+
def cloud_watch_rule_name(prefix)
|
35
|
+
"#{prefix}#{@name}-#{hashcode}".gsub(/[^\.\-_A-Za-z0-9]/, "-").downcase
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def hashcode
|
41
|
+
@hashcode ||= OpenSSL::Digest::SHA256.hexdigest("#{@name}\t#{@description}\t#{@schedule}\t#{@command}")[0..12]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Xronor
|
2
|
+
class Parser
|
3
|
+
def self.parse(filename)
|
4
|
+
body = open(filename).read
|
5
|
+
result = Xronor::DSL.eval(body).result
|
6
|
+
|
7
|
+
result.jobs.values.map do |job|
|
8
|
+
job.description ||= job.name
|
9
|
+
Xronor::Job.new(job.name, job.description, job.schedule, job.command)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/xronor.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "aws-sdk-core"
|
2
|
+
require "active_support/core_ext/time"
|
3
|
+
require "chronic"
|
4
|
+
require "erb"
|
5
|
+
require "openssl"
|
6
|
+
require "optparse"
|
7
|
+
require "shellwords"
|
8
|
+
require "thor"
|
9
|
+
|
10
|
+
require "xronor/aws/cloud_watch_events"
|
11
|
+
require "xronor/aws/dynamo_db"
|
12
|
+
require "xronor/aws/lambda"
|
13
|
+
require "xronor/cli"
|
14
|
+
require "xronor/core_ext/numeric"
|
15
|
+
require "xronor/dsl"
|
16
|
+
require "xronor/dsl/checker"
|
17
|
+
require "xronor/dsl/default"
|
18
|
+
require "xronor/dsl/job"
|
19
|
+
require "xronor/dsl/numeric_seconds"
|
20
|
+
require "xronor/dsl/schedule_converter"
|
21
|
+
require "xronor/generator/crontab"
|
22
|
+
require "xronor/generator/cloud_watch_events"
|
23
|
+
require "xronor/generator/erb"
|
24
|
+
require "xronor/job"
|
25
|
+
require "xronor/parser"
|
26
|
+
require "xronor/version"
|
27
|
+
|
28
|
+
module Xronor
|
29
|
+
|
30
|
+
end
|
data/xronor.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'xronor/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "xronor"
|
8
|
+
spec.version = Xronor::VERSION
|
9
|
+
spec.authors = ["Daisuke Fujita"]
|
10
|
+
spec.email = ["dtanshi45@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Whenever DSL -> CloudWatch Events Schedule Rule & ECS Task Parameter}
|
13
|
+
spec.description = %q{Convert Whenever DSL to CloudWatch Events Schedule Rule to invoke job on ECS}
|
14
|
+
spec.homepage = "https://github.com/dtan4/xronor"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_dependency "activesupport", "~> 5.0.2"
|
25
|
+
spec.add_dependency "aws-sdk", "~> 2.8.7"
|
26
|
+
spec.add_dependency "chronic", "~> 0.10"
|
27
|
+
spec.add_dependency "thor", "~> 0.19"
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
32
|
+
end
|