elastic_whenever 0.2.0 → 0.3.0

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
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