elastic_whenever 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 279fa293930adedd6b35d4fbf629c8b647c09b35
4
- data.tar.gz: d2949e35f69c75489f38fc9e9b45a655c06de5b4
3
+ metadata.gz: fdc3b1d145e38b2bd8850b9308b5317f6077c0cc
4
+ data.tar.gz: 49262310bd75b89f1cc22ef009380fd5c98d9e65
5
5
  SHA512:
6
- metadata.gz: bdd1cf72a17f318c78ad1e27fb6340761e064ace66c4a45b8942437dc302a8a06b984a0cf16b95afad9199c13b9d650c8d56be5fbb29471d7afc33d2d5599301
7
- data.tar.gz: 74528044ab14b01dfdf3c40910b2049ffae4b06c56476033d2374910e3a5c58e45793bb7a5f06f70f41a314d1dd6ecd2b2dc56414a5a8500489c30080c10ae6b
6
+ metadata.gz: 3ab0c5534ff5e58d532dac65846564787d3916645518dba404800072f61464ef037a6d2ef4a6e555b99ceb20841c81667ac2d769f7cdc9d4b86bff9a4b4fc7a7
7
+ data.tar.gz: ff7a4114ef6840ea85c780b8b7418896cae7523b4acbbc5680100cd77b4240c81257fd3cff63366fc21d788f41643f9ab66581659a2204bb64c8e718878912c0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Kazuma Watanabe
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 CHANGED
@@ -1,4 +1,7 @@
1
1
  # Elastic Whenever
2
+ [![Build Status](https://travis-ci.org/wata727/elastic_whenever.svg?branch=master)](https://travis-ci.org/wata727/elastic_whenever)
3
+ [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE.txt)
4
+ [![Gem Version](https://badge.fury.io/rb/elastic_whenever.svg)](https://badge.fury.io/rb/elastic_whenever)
2
5
 
3
6
  Manage ECS scheduled tasks like whenever gem.
4
7
 
@@ -67,6 +70,148 @@ cron(0 3 * * ? *) ecs-test example:2 cron bundle exec rake hoge:run
67
70
  ## [message] Run `elastic_whenever --help' for more options.
68
71
  ```
69
72
 
73
+ ## How it works
74
+ Elastic Whenever creates CloudWatch Events as many as `every` block. The number of jobs declared in it corresponds to the target.
75
+
76
+ ```ruby
77
+ every '0 0 * * *' do # scheduled task (identifier_68237a3b152a6c44359e1cf5cd8e7cf0def303d7)
78
+ rake "hoge:run" # target for `identifier_68237a3b152a6c44359e1cf5cd8e7cf0def303d7`
79
+ command "awesome" # target for `identifier_68237a3b152a6c44359e1cf5cd8e7cf0def303d7`
80
+ end
81
+ ```
82
+
83
+ The name of the scheduled task is the identifier passed in CLI and the digest value calculated from the command etc.
84
+ Because CloudWatch Events rule names are unique across all clusters, you should not use the same identifier across different clusters.
85
+
86
+ ## Compatible with Whenever
87
+ ### `job_type` method is not supported
88
+ Whenever supports custom job type using `job_type` method, but Elastic Whenever does not support it.
89
+
90
+ ```ruby
91
+ # [warn] Skipping unsupported method: job_type
92
+ job_type :awesome, '/usr/local/bin/awesome :task :fun_level'
93
+ ```
94
+
95
+ ### `env` method is not supported
96
+ Whenever supports environment variables using `env` method, but Elastic Whenever does not support it.
97
+ You should use task definitions to set environment variables.
98
+
99
+ ```ruby
100
+ # [warn] Skipping unsupported method: env
101
+ env "VERSION", "v1"
102
+ ```
103
+
104
+ ### `:job_template` option is not supported.
105
+ Whenever has a template to describe as cron, but Elastic Whenever does not have the template.
106
+ Therefore, `:job_template` option is ignored.
107
+
108
+ ```ruby
109
+ set :job_template, "/bin/zsh -l -c ':job'" # ignored
110
+ ```
111
+
112
+ ### Behavior of frequency
113
+ Elastic Whenever processes the frequency received by `every` block almost like whenever.
114
+
115
+ ```ruby
116
+ # Whenever
117
+ # 0 15 * * * /bin/bash -l -c 'cd /home/user/app && RAILS_ENV=production bundle exec rake hoge:run --silent'
118
+ #
119
+ # Elastic Whenever
120
+ # cron(0 15 * * ? *) ecs-test myapp:2 web bundle exec rake hoge:run --silent
121
+ #
122
+ every :day, at: "3:00" do
123
+ rake "hoge:run"
124
+ end
125
+
126
+ # Whenever
127
+ # 0,10,20,30,40,50 * * * * /bin/bash -l -c 'awesome'
128
+ #
129
+ # Elastic Whenever
130
+ # cron(0,10,20,30,40,50 * * * ? *) ecs-test myapp:2 web awesome
131
+ #
132
+ every 10.minutes do
133
+ command "awesome"
134
+ end
135
+ ```
136
+
137
+ However, handling of the day of week is partially different because it follows the scheduled expression.
138
+
139
+ ```ruby
140
+ # Whenever
141
+ # 0 0 * * 1 /bin/bash -l -c 'awesome'
142
+ #
143
+ # Elastic Whenever
144
+ # cron(0 0 ? * 2 *) ecs-test myapp:2 web awesome
145
+ #
146
+ every :monday do
147
+ command "awesome"
148
+ end
149
+ ```
150
+
151
+ Therefore, the cron syntax is converted to scheduled tasks.
152
+
153
+ ```ruby
154
+ # cron(0 0 ? * 2 *) ecs-test myapp:2 web awesome
155
+ every "0 0 * * 1" do
156
+ command "awesome"
157
+ end
158
+ ```
159
+
160
+ Of course, you can write the scheduled expression.
161
+
162
+ ```ruby
163
+ # cron(0 0 ? * 2 *) ecs-test myapp:2 web awesome
164
+ every "0 0 ? * 2 *" do
165
+ command "awesome"
166
+ end
167
+ ```
168
+
169
+ #### `:reboot` option is not supported
170
+ Whenever supports `:reboot` which is a function of cron, but Elastic Whenever does not support it.
171
+
172
+ ```ruby
173
+ # [warn] `reboot` is not supported option. Ignore this task.
174
+ every :reboot do
175
+ rake "hoge:run"
176
+ end
177
+ ```
178
+
179
+ ### Behavior of bundle commands
180
+ Whenever checks if the application uses bundler and automatically adds the prefix to commands.
181
+ However, Elastic Whenever always adds the prefix on the premise that the application is using bundler.
182
+
183
+ ```ruby
184
+ # Whenever
185
+ # With bundler -> bundle exec rake hoge:run
186
+ # Without bundler -> rake hoge:run
187
+ #
188
+ # Elastic Whenever
189
+ # bundle exec rake hoge:run
190
+ #
191
+ rake "hoge:run"
192
+ ```
193
+
194
+ If you don't want to add the prefix, set `bundle_command` to empty as follows:
195
+
196
+ ```ruby
197
+ set :bundle_command, ""
198
+ ```
199
+
200
+ ### Drop support for for Rails 3 or below
201
+ Whenever supports `runner` job with old Rails version, but Elastic Whenever supports Rails 4 and above only.
202
+
203
+ ```ruby
204
+ # Whenever
205
+ # Before them -> script/runner Hoge.run
206
+ # Rails 3 -> script/rails runner Hoge.run
207
+ # Rails 4 -> bin/rails runner Hoge.run
208
+ #
209
+ # Elastic Whenever
210
+ # bin/rails runner Hoge.run
211
+ #
212
+ runner "Hoge.run"
213
+ ```
214
+
70
215
  ## Development
71
216
 
72
217
  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.
@@ -36,7 +36,8 @@ module ElasticWhenever
36
36
  ERROR_EXIT_CODE
37
37
  rescue OptionParser::MissingArgument,
38
38
  Option::InvalidOptionException,
39
- Schedule::InvalidScheduleException => exn
39
+ Schedule::InvalidScheduleException,
40
+ Task::Target::InvalidContainerException => exn
40
41
 
41
42
  Logger.instance.fail(exn.message)
42
43
  ERROR_EXIT_CODE
@@ -57,12 +58,7 @@ module ElasticWhenever
57
58
 
58
59
  clear_tasks(option) unless dry_run
59
60
  schedule.tasks.each do |task|
60
- begin
61
- rule = Task::Rule.convert(option, task, schedule.chronic_options)
62
- rescue Task::Rule::UnsupportedOptionException => exn
63
- Logger.instance.warn(exn.message)
64
- next
65
- end
61
+ rule = Task::Rule.convert(option, task)
66
62
  targets = task.commands.map do |command|
67
63
  Task::Target.new(
68
64
  option,
@@ -8,6 +8,47 @@ module ElasticWhenever
8
8
  attr_reader :bundle_command
9
9
 
10
10
  class InvalidScheduleException < StandardError; end
11
+ class UnsupportedFrequencyException < StandardError; end
12
+
13
+ module WheneverNumeric
14
+ refine Numeric do
15
+ def seconds
16
+ self
17
+ end
18
+ alias :second :seconds
19
+
20
+ def minutes
21
+ self * 60
22
+ end
23
+ alias :minute :minutes
24
+
25
+ def hours
26
+ (self * 60).minutes
27
+ end
28
+ alias :hour :hours
29
+
30
+ def days
31
+ (self * 24).hours
32
+ end
33
+ alias :day :days
34
+
35
+ def weeks
36
+ (self * 7).days
37
+ end
38
+ alias :week :weeks
39
+
40
+ def months
41
+ (self * 30).days
42
+ end
43
+ alias :month :months
44
+
45
+ def years
46
+ (self * 365.25).days
47
+ end
48
+ alias :year :years
49
+ end
50
+ end
51
+ using WheneverNumeric
11
52
 
12
53
  def initialize(file, variables)
13
54
  @environment = "production"
@@ -27,9 +68,11 @@ module ElasticWhenever
27
68
  end
28
69
 
29
70
  def every(frequency, options = {}, &block)
30
- @tasks << Task.new(@environment, @bundle_command, frequency, options).tap do |task|
71
+ @tasks << Task.new(@environment, @bundle_command, schedule_expression(frequency, options)).tap do |task|
31
72
  task.instance_eval(&block)
32
73
  end
74
+ rescue UnsupportedFrequencyException => exn
75
+ Logger.instance.warn(exn.message)
33
76
  end
34
77
 
35
78
  def validate!
@@ -38,6 +81,83 @@ module ElasticWhenever
38
81
  raise InvalidScheduleException.new("You must set container") unless container
39
82
  end
40
83
 
84
+ def schedule_expression(frequency, options)
85
+ opts = { :now => Time.new(2017, 12, 1, 0, 0, 0) }.merge(@chronic_options)
86
+ time = Chronic.parse(options[:at], opts) || Time.new(2017, 12, 1, 0, 0, 0)
87
+
88
+ case frequency
89
+ when 1.minute
90
+ "cron(* * * * ? *)"
91
+ when :hour, 1.hour
92
+ "cron(#{time.min} * * * ? *)"
93
+ when :day, 1.day
94
+ "cron(#{time.min} #{time.hour} * * ? *)"
95
+ when :month, 1.month
96
+ "cron(#{time.min} #{time.hour} #{time.day} * ? *)"
97
+ when :year, 1.year
98
+ "cron(#{time.min} #{time.hour} #{time.day} #{time.month} ? *)"
99
+ when :sunday
100
+ "cron(#{time.min} #{time.hour} ? * 1 *)"
101
+ when :monday
102
+ "cron(#{time.min} #{time.hour} ? * 2 *)"
103
+ when :tuesday
104
+ "cron(#{time.min} #{time.hour} ? * 3 *)"
105
+ when :wednesday
106
+ "cron(#{time.min} #{time.hour} ? * 4 *)"
107
+ when :thursday
108
+ "cron(#{time.min} #{time.hour} ? * 5 *)"
109
+ when :friday
110
+ "cron(#{time.min} #{time.hour} ? * 6 *)"
111
+ when :saturday
112
+ "cron(#{time.min} #{time.hour} ? * 7 *)"
113
+ when :weekend
114
+ "cron(#{time.min} #{time.hour} ? * 1,7 *)"
115
+ when :weekday
116
+ "cron(#{time.min} #{time.hour} ? * 2-6 *)"
117
+ when 1.second...1.minute
118
+ raise UnsupportedFrequencyException.new("Time must be in minutes or higher. Ignore this task.")
119
+ when 1.minute...1.hour
120
+ step = (frequency / 60).round
121
+ min = []
122
+ (60 % step == 0 ? 0 : step).step(59, step) { |i| min << i }
123
+ "cron(#{min.join(",")} * * * ? *)"
124
+ when 1.hour...1.day
125
+ step = (frequency / 60 / 60).round
126
+ hour = []
127
+ (24 % step == 0 ? 0 : step).step(23, step) { |i| hour << i }
128
+ "cron(#{time.min} #{hour.join(",")} * * ? *)"
129
+ when 1.day...1.month
130
+ step = (frequency / 24 / 60 / 60).round
131
+ day = []
132
+ (step <= 16 ? 1 : step).step(30, step) { |i| day << i }
133
+ "cron(#{time.min} #{time.hour} #{day.join(",")} * ? *)"
134
+ when 1.month...12.months
135
+ step = (frequency / 30 / 24 / 60 / 60).round
136
+ month = []
137
+ (step <= 6 ? 1 : step).step(12, step) { |i| month << i }
138
+ "cron(#{time.min} #{time.hour} #{time.day} #{month.join(",")} ? *)"
139
+ when 12.months...Float::INFINITY
140
+ raise UnsupportedFrequencyException.new("Time must be in months or lower. Ignore this task.")
141
+ # cron syntax
142
+ when /^((\*?[\d\/,\-]*)\s*){5}$/
143
+ min, hour, day, mon, week, year = frequency.split(" ")
144
+ # You can't specify the Day-of-month and Day-of-week fields in the same Cron expression.
145
+ # If you specify a value in one of the fields, you must use a ? (question mark) in the other.
146
+ week.gsub!("*", "?") if day != "?"
147
+ day.gsub!("*", "?") if week != "?"
148
+ # cron syntax: sunday -> 0
149
+ # scheduled expression: sunday -> 1
150
+ week.gsub!(/(\d)/) { (Integer($1) + 1) % 7 }
151
+ year = year || "*"
152
+ "cron(#{min} #{hour} #{day} #{mon} #{week} #{year})"
153
+ # schedule expression syntax
154
+ when /^((\*?\??L?W?[\d\/,\-]*)\s*){6}$/
155
+ "cron(#{frequency})"
156
+ else
157
+ raise UnsupportedFrequencyException.new("`#{frequency}` is not supported option. Ignore this task.")
158
+ end
159
+ end
160
+
41
161
  def method_missing(name, *args)
42
162
  Logger.instance.warn("Skipping unsupported method: #{name}")
43
163
  end
@@ -1,14 +1,12 @@
1
1
  module ElasticWhenever
2
2
  class Task
3
3
  attr_reader :commands
4
- attr_reader :frequency
5
- attr_reader :options
4
+ attr_reader :expression
6
5
 
7
- def initialize(environment, bundle_command, frequency, options = {})
6
+ def initialize(environment, bundle_command, expression)
8
7
  @environment = environment
9
8
  @bundle_command = bundle_command.split(" ")
10
- @frequency = frequency
11
- @options = options
9
+ @expression = expression
12
10
  @commands = []
13
11
  end
14
12
 
@@ -16,10 +16,14 @@ module ElasticWhenever
16
16
  definition&.task_definition_arn
17
17
  end
18
18
 
19
+ def containers
20
+ definition&.container_definitions&.map(&:name)
21
+ end
22
+
19
23
  private
20
24
 
21
25
  attr_reader :client
22
26
  attr_reader :definition
23
27
  end
24
28
  end
25
- end
29
+ end
@@ -17,11 +17,11 @@ module ElasticWhenever
17
17
  end
18
18
  end
19
19
 
20
- def self.convert(option, task, chronic_options)
20
+ def self.convert(option, task)
21
21
  self.new(
22
22
  option,
23
23
  name: rule_name(option.identifier, task.commands),
24
- expression: schedule_expression(task.frequency, task.options, chronic_options)
24
+ expression: task.expression
25
25
  )
26
26
  end
27
27
 
@@ -51,56 +51,6 @@ module ElasticWhenever
51
51
  "#{identifier}_#{Digest::SHA1.hexdigest(commands.map { |command| command.join("-") }.join("-"))}"
52
52
  end
53
53
 
54
- def self.schedule_expression(frequency, options, chronic_options)
55
- time = Chronic.parse(options[:at], chronic_options) || Time.new(2017, 12, 1, 0, 0, 0)
56
-
57
- case frequency
58
- when :hour
59
- "cron(#{time.min} * * * ? *)"
60
- when :day
61
- "cron(#{time.min} #{time.hour} * * ? *)"
62
- when :month
63
- "cron(#{time.min} #{time.hour} #{time.day} * ? *)"
64
- when :year
65
- "cron(#{time.min} #{time.hour} #{time.day} #{time.month} ? *)"
66
- when :sunday
67
- "cron(#{time.min} #{time.hour} ? * 1 *)"
68
- when :monday
69
- "cron(#{time.min} #{time.hour} ? * 2 *)"
70
- when :tuesday
71
- "cron(#{time.min} #{time.hour} ? * 3 *)"
72
- when :wednesday
73
- "cron(#{time.min} #{time.hour} ? * 4 *)"
74
- when :thursday
75
- "cron(#{time.min} #{time.hour} ? * 5 *)"
76
- when :friday
77
- "cron(#{time.min} #{time.hour} ? * 6 *)"
78
- when :saturday
79
- "cron(#{time.min} #{time.hour} ? * 7 *)"
80
- when :weekend
81
- "cron(#{time.min} #{time.hour} ? * 1,7 *)"
82
- when :weekday
83
- "cron(#{time.min} #{time.hour} ? * 2-6 *)"
84
- # cron syntax
85
- when /^((\*?[\d\/,\-]*)\s*){5}$/
86
- min, hour, day, mon, week, year = frequency.split(" ")
87
- # You can't specify the Day-of-month and Day-of-week fields in the same Cron expression.
88
- # If you specify a value in one of the fields, you must use a ? (question mark) in the other.
89
- week.gsub!("*", "?") if day != "?"
90
- day.gsub!("*", "?") if week != "?"
91
- # cron syntax: sunday -> 0
92
- # scheduled expression: sunday -> 1
93
- week.gsub!(/(\d)/) { (Integer($1) + 1) % 7 }
94
- year = year || "*"
95
- "cron(#{min} #{hour} #{day} #{mon} #{week} #{year})"
96
- # schedule expression syntax
97
- when /^((\*?\??L?W?[\d\/,\-]*)\s*){6}$/
98
- "cron(#{frequency})"
99
- else
100
- raise UnsupportedOptionException.new("`#{frequency}` is not supported option. Ignore this task.")
101
- end
102
- end
103
-
104
54
  attr_reader :client
105
55
  end
106
56
  end
@@ -6,6 +6,8 @@ module ElasticWhenever
6
6
  attr_reader :container
7
7
  attr_reader :commands
8
8
 
9
+ class InvalidContainerException < StandardError; end
10
+
9
11
  def self.fetch(option, rule)
10
12
  client = Aws::CloudWatchEvents::Client.new(option.aws_config)
11
13
  targets = client.list_targets_by_rule(rule: rule.name).targets
@@ -25,6 +27,10 @@ module ElasticWhenever
25
27
  end
26
28
 
27
29
  def initialize(option, cluster:, definition:, container:, commands:, rule:, role:)
30
+ unless definition.containers.include?(container)
31
+ raise InvalidContainerException.new("#{container} is invalid container. valid=#{definition.containers.join(",")}")
32
+ end
33
+
28
34
  @cluster = cluster
29
35
  @definition = definition
30
36
  @container = container
@@ -70,4 +76,4 @@ module ElasticWhenever
70
76
  attr_reader :client
71
77
  end
72
78
  end
73
- end
79
+ end
@@ -1,3 +1,3 @@
1
1
  module ElasticWhenever
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic_whenever
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuma Watanabe
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-09-21 00:00:00.000000000 Z
11
+ date: 2017-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -120,6 +120,7 @@ files:
120
120
  - ".rspec"
121
121
  - ".travis.yml"
122
122
  - Gemfile
123
+ - LICENSE.txt
123
124
  - README.md
124
125
  - Rakefile
125
126
  - bin/console
@@ -157,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
158
  version: '0'
158
159
  requirements: []
159
160
  rubyforge_project:
160
- rubygems_version: 2.6.13
161
+ rubygems_version: 2.6.11
161
162
  signing_key:
162
163
  specification_version: 4
163
164
  summary: Manage ECS Scheduled Tasks like Whenever