elastic_whenever 0.5.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +26 -0
- data/CHANGELOG.md +39 -0
- data/README.md +29 -6
- data/elastic_whenever.gemspec +2 -1
- data/exe/elastic_whenever +1 -1
- data/lib/elastic_whenever/cli.rb +124 -98
- data/lib/elastic_whenever/option.rb +45 -2
- data/lib/elastic_whenever/task/cluster.rb +1 -1
- data/lib/elastic_whenever/task/definition.rb +16 -2
- data/lib/elastic_whenever/task/role.rb +1 -1
- data/lib/elastic_whenever/task/rule.rb +43 -12
- data/lib/elastic_whenever/task/target.rb +4 -4
- data/lib/elastic_whenever/version.rb +1 -1
- metadata +20 -6
- data/.travis.yml +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: faaf28462435852a35046ac97c388f60eaaa3348f9b386251960d83d4351f3a5
|
4
|
+
data.tar.gz: c35bf7a5281b25667c3a96dda86920a6b304ba35a147d2421105233f5843fb69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5639992267344f3360d8115ae4ad4beef874bea395d666c0840f80bc04a736c098a781f94b8f29cf4c0f0174ec875caf92c2a3ce0d311657efd91427313b18b
|
7
|
+
data.tar.gz: 4165f7c3d71f02f991e4d509f3a6493c2ff36ee11d1733bda7def83825da07bf468796fe83e079603ca974cc9981d63c0e53055fac00339ccc327bee17cdba3a
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: build
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
branches:
|
9
|
+
- master
|
10
|
+
schedule:
|
11
|
+
- cron: "0 0 * * *"
|
12
|
+
|
13
|
+
jobs:
|
14
|
+
spec:
|
15
|
+
name: Ruby ${{ matrix.ruby_version }}
|
16
|
+
runs-on: ubuntu-latest
|
17
|
+
strategy:
|
18
|
+
matrix:
|
19
|
+
ruby_version: ['2.6', '2.7', '3.0']
|
20
|
+
steps:
|
21
|
+
- uses: actions/checkout@v2
|
22
|
+
- uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
ruby-version: ${{ matrix.ruby_version }}
|
25
|
+
bundler-cache: true
|
26
|
+
- run: bundle exec rake
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,42 @@
|
|
1
|
+
## v0.7.0 (2021-09-25)
|
2
|
+
|
3
|
+
This release contains a major change to the behavior when updating tasks. In most cases, this change has no effect, but be aware of the change in behavior when omitting a revision of the task definition. In particular, if you are building a deployment workflow where the update timing of task definitions and the update timing of scheduled tasks are different, the revisions that are executed may be different.
|
4
|
+
|
5
|
+
### Breaking Changes
|
6
|
+
|
7
|
+
- [#57](https://github.com/wata727/elastic_whenever/pull/57): Selective Updates ([@HistoireDeBabar](https://github.com/HistoireDeBabar))
|
8
|
+
- Previously, Elastic Whenever recreates all scheduled tasks after deleting all tasks when updating tasks. However, in this case, there is a risk that frequently invoked tasks will not be executed, so this change now creates/deletes only those tasks that have changed.
|
9
|
+
- Due to this change, the naming convention for scheduled tasks has changed. When updating from v0.6, all rules will be deleted and recreated due to different naming conventions, and the behavior will be the same as before. After that, the task will not change if the names are the same.
|
10
|
+
- The breaking change is that the behavior when omitting a revision of a task definition has changed. Previously, when you created a task, you resolved the latest revision at that time, so even if the task definition was updated, the revision that was executed was always the same. In v0.7 and later, revisions are not resolved when you created a task, so the latest revision is always adopted.
|
11
|
+
|
12
|
+
### Chores
|
13
|
+
|
14
|
+
- [#55](https://github.com/wata727/elastic_whenever/pull/55): CI against Ruby 3.0
|
15
|
+
- [#56](https://github.com/wata727/elastic_whenever/pull/56): Add the rexml dependency explictly
|
16
|
+
- [#58](https://github.com/wata727/elastic_whenever/pull/58): Fix typo
|
17
|
+
|
18
|
+
## v0.6.1 (2020-11-08)
|
19
|
+
|
20
|
+
### BugFixes
|
21
|
+
|
22
|
+
- [#51](https://github.com/wata727/elastic_whenever/pull/51): Avoid hitting rate limits fetching credentials when running under ECS or with an IAM profile ([@stevenwilliamson](https://github.com/stevenwilliamson))
|
23
|
+
|
24
|
+
### Chores
|
25
|
+
|
26
|
+
- [#53](https://github.com/wata727/elastic_whenever/pull/53): Migrate CI to GitHub Actions from Travis CI
|
27
|
+
|
28
|
+
## v0.6.0 (2019-10-16)
|
29
|
+
|
30
|
+
### Enhancements
|
31
|
+
|
32
|
+
- [#46](https://github.com/wata727/elastic_whenever/pull/46): Add option to disable rule ([@tobscher](https://github.com/tobscher))
|
33
|
+
|
34
|
+
## v0.5.1 (2019-10-09)
|
35
|
+
|
36
|
+
### Enhancements
|
37
|
+
|
38
|
+
- [#45](https://github.com/wata727/elastic_whenever/pull/45): Add description to CloudWatch rule ([@tobscher](https://github.com/tobscher))
|
39
|
+
|
1
40
|
## v0.5.0 (2019-09-19)
|
2
41
|
|
3
42
|
### Enhancements
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Elastic Whenever
|
2
|
-
[![Build Status](https://
|
2
|
+
[![Build Status](https://github.com/wata727/elastic_whenever/workflows/build/badge.svg?branch=master)](https://github.com/wata727/elastic_whenever/actions)
|
3
3
|
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE.txt)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/elastic_whenever.svg)](https://badge.fury.io/rb/elastic_whenever)
|
5
5
|
|
@@ -43,6 +43,7 @@ Usage: elastic_whenever [options]
|
|
43
43
|
--platform-version version Optionally specify the platform version. Default: LATEST (FARGATE only)
|
44
44
|
-f, --file schedule_file Default: config/schedule.rb
|
45
45
|
--iam-role name IAM role name used by CloudWatch Events. Default: ecsEventsRole
|
46
|
+
--rule-state state The state of the CloudWatch Events Rule (ENABLED or DISABLED), default: ENABLED
|
46
47
|
--profile profile_name AWS shared profile name
|
47
48
|
--access-key aws_access_key_id
|
48
49
|
AWS access key ID
|
@@ -63,13 +64,35 @@ cron(0 3 * * ? *) ecs-test example:2 cron bundle exec rake hoge:run
|
|
63
64
|
## [message] Run `elastic_whenever --help' for more options.
|
64
65
|
```
|
65
66
|
|
67
|
+
### Setting variables
|
68
|
+
Elastic Whenever supports setting variables via the `--set` option [as Whenever does](https://github.com/javan/whenever/wiki/Setting-variables-on-the-fly).
|
69
|
+
|
70
|
+
Example:
|
71
|
+
|
72
|
+
`elastic_whenever --set 'environment=staging&some_var=foo'`
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
if @environment == 'staging'
|
76
|
+
every '0 1 * * *' do
|
77
|
+
rake 'some_task_on_staging'
|
78
|
+
end
|
79
|
+
elsif @some_var == 'foo'
|
80
|
+
every '0 10 * * *' do
|
81
|
+
rake 'some_task'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
Especially, `@environment` defaults to `"production"`.
|
87
|
+
|
66
88
|
## How it works
|
67
|
-
Elastic Whenever creates CloudWatch Events
|
89
|
+
Elastic Whenever creates CloudWatch Events for every command. Each rule has a one to one mapping to a target.
|
90
|
+
for example, the following input will generate two Rules each with one Target.
|
68
91
|
|
69
92
|
```ruby
|
70
|
-
every '0 0 * * *' do
|
71
|
-
rake "hoge:run"
|
72
|
-
command "awesome"
|
93
|
+
every '0 0 * * *' do
|
94
|
+
rake "hoge:run"
|
95
|
+
command "awesome"
|
73
96
|
end
|
74
97
|
```
|
75
98
|
|
@@ -77,7 +100,7 @@ The scheduled task's name is a digest value calculated from an identifier, comma
|
|
77
100
|
|
78
101
|
NOTE: You should not use the same identifier across different clusters because CloudWatch Events rule names are unique across all clusters.
|
79
102
|
|
80
|
-
##
|
103
|
+
## Compatibility with Whenever
|
81
104
|
### `job_type`
|
82
105
|
Whenever supports custom job type with `job_type` method, but Elastic Whenever doesn't support it.
|
83
106
|
|
data/elastic_whenever.gemspec
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
23
|
spec.add_development_dependency "bundler", "~> 2.0"
|
24
|
-
spec.add_development_dependency "rake", "~>
|
24
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
25
25
|
spec.add_development_dependency "rspec", "~> 3.0"
|
26
26
|
|
27
27
|
spec.add_dependency "aws-sdk-ecs", "~> 1.0"
|
@@ -29,4 +29,5 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_dependency "aws-sdk-iam", "~> 1.0"
|
30
30
|
spec.add_dependency "chronic", "~> 0.10"
|
31
31
|
spec.add_dependency "retryable", "~> 3.0"
|
32
|
+
spec.add_dependency "rexml", ">= 0"
|
32
33
|
end
|
data/exe/elastic_whenever
CHANGED
data/lib/elastic_whenever/cli.rb
CHANGED
@@ -3,123 +3,149 @@ module ElasticWhenever
|
|
3
3
|
SUCCESS_EXIT_CODE = 0
|
4
4
|
ERROR_EXIT_CODE = 1
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
update_tasks(option, dry_run: true)
|
13
|
-
Logger.instance.message("Above is your schedule file converted to scheduled tasks; your scheduled tasks was not updated.")
|
14
|
-
Logger.instance.message("Run `elastic_whenever --help' for more options.")
|
15
|
-
when Option::UPDATE_MODE
|
16
|
-
option.validate!
|
17
|
-
with_concurrent_modification_handling do
|
18
|
-
update_tasks(option, dry_run: false)
|
19
|
-
end
|
20
|
-
Logger.instance.log("write", "scheduled tasks updated")
|
21
|
-
when Option::CLEAR_MODE
|
22
|
-
with_concurrent_modification_handling do
|
23
|
-
clear_tasks(option)
|
24
|
-
end
|
25
|
-
Logger.instance.log("write", "scheduled tasks cleared")
|
26
|
-
when Option::LIST_MODE
|
27
|
-
list_tasks(option)
|
28
|
-
Logger.instance.message("Above is your scheduled tasks.")
|
29
|
-
Logger.instance.message("Run `elastic_whenever --help` for more options.")
|
30
|
-
when Option::PRINT_VERSION_MODE
|
31
|
-
print_version
|
32
|
-
end
|
6
|
+
attr_reader :args, :option
|
7
|
+
|
8
|
+
def initialize(args)
|
9
|
+
@args = args
|
10
|
+
@option = Option.new(args)
|
11
|
+
end
|
33
12
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
Logger.instance.
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
13
|
+
def run
|
14
|
+
case option.mode
|
15
|
+
when Option::DRYRUN_MODE
|
16
|
+
option.validate!
|
17
|
+
update_tasks(dry_run: true)
|
18
|
+
Logger.instance.message("Above is your schedule file converted to scheduled tasks; your scheduled tasks was not updated.")
|
19
|
+
Logger.instance.message("Run `elastic_whenever --help' for more options.")
|
20
|
+
when Option::UPDATE_MODE
|
21
|
+
option.validate!
|
22
|
+
with_concurrent_modification_handling do
|
23
|
+
update_tasks(dry_run: false)
|
24
|
+
end
|
25
|
+
Logger.instance.log("write", "scheduled tasks updated")
|
26
|
+
when Option::CLEAR_MODE
|
27
|
+
with_concurrent_modification_handling do
|
28
|
+
clear_tasks
|
29
|
+
end
|
30
|
+
Logger.instance.log("write", "scheduled tasks cleared")
|
31
|
+
when Option::LIST_MODE
|
32
|
+
list_tasks
|
33
|
+
Logger.instance.message("Above is your scheduled tasks.")
|
34
|
+
Logger.instance.message("Run `elastic_whenever --help` for more options.")
|
35
|
+
when Option::PRINT_VERSION_MODE
|
36
|
+
print_version
|
47
37
|
end
|
48
38
|
|
49
|
-
|
39
|
+
SUCCESS_EXIT_CODE
|
40
|
+
rescue Aws::Errors::MissingRegionError
|
41
|
+
Logger.instance.fail("missing region error occurred; please use `--region` option or export `AWS_REGION` environment variable.")
|
42
|
+
ERROR_EXIT_CODE
|
43
|
+
rescue Aws::Errors::MissingCredentialsError => e
|
44
|
+
Logger.instance.fail("missing credential error occurred; please specify it with arguments, use shared credentials, or export `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variable")
|
45
|
+
ERROR_EXIT_CODE
|
46
|
+
rescue OptionParser::MissingArgument,
|
47
|
+
Option::InvalidOptionException,
|
48
|
+
Task::Target::InvalidContainerException => exn
|
50
49
|
|
51
|
-
|
52
|
-
|
50
|
+
Logger.instance.fail(exn.message)
|
51
|
+
ERROR_EXIT_CODE
|
52
|
+
end
|
53
53
|
|
54
|
-
|
55
|
-
definition = Task::Definition.new(option, option.task_definition)
|
56
|
-
role = Task::Role.new(option)
|
57
|
-
if !role.exists? && !dry_run
|
58
|
-
role.create
|
59
|
-
end
|
54
|
+
private
|
60
55
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
Logger.instance.warn("#{exn.message} Ignore this task: name=#{rule.name} expression=#{rule.expression}")
|
83
|
-
next
|
84
|
-
end
|
85
|
-
targets.each(&:create)
|
86
|
-
end
|
56
|
+
def update_tasks(dry_run:)
|
57
|
+
schedule = Schedule.new(option.schedule_file, option.verbose, option.variables)
|
58
|
+
|
59
|
+
cluster = Task::Cluster.new(option, option.cluster)
|
60
|
+
definition = Task::Definition.new(option, option.task_definition)
|
61
|
+
role = Task::Role.new(option)
|
62
|
+
if !role.exists? && !dry_run
|
63
|
+
role.create
|
64
|
+
end
|
65
|
+
|
66
|
+
targets = schedule.tasks.map do |task|
|
67
|
+
task.commands.map do |command|
|
68
|
+
Task::Target.new(
|
69
|
+
option,
|
70
|
+
cluster: cluster,
|
71
|
+
definition: definition,
|
72
|
+
container: option.container,
|
73
|
+
commands: command,
|
74
|
+
rule: Task::Rule.convert(option, task.expression, command),
|
75
|
+
role: role,
|
76
|
+
)
|
87
77
|
end
|
78
|
+
end.flatten
|
79
|
+
|
80
|
+
if dry_run
|
81
|
+
print_task(targets)
|
82
|
+
else
|
83
|
+
create_missing_rules_from_targets(targets)
|
84
|
+
delete_unused_rules_from_targets(targets)
|
88
85
|
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def remote_rules
|
89
|
+
Task::Rule.fetch(option)
|
90
|
+
end
|
89
91
|
|
90
|
-
|
91
|
-
|
92
|
+
# Creates a rule but only persists the rule remotely if it does not exist
|
93
|
+
def create_missing_rules_from_targets(targets)
|
94
|
+
cached_remote_rules = remote_rules
|
95
|
+
targets.each do |target|
|
96
|
+
exists = cached_remote_rules.any? do |remote_rule|
|
97
|
+
target.rule.name == remote_rule.name
|
98
|
+
end
|
99
|
+
|
100
|
+
unless exists
|
101
|
+
target.rule.create
|
102
|
+
target.create
|
103
|
+
end
|
92
104
|
end
|
105
|
+
end
|
93
106
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
107
|
+
def delete_unused_rules_from_targets(targets)
|
108
|
+
remote_rules.each do |remote_rule|
|
109
|
+
rule_exists_in_schedule = targets.any? do |target|
|
110
|
+
target.rule.name == remote_rule.name
|
98
111
|
end
|
112
|
+
|
113
|
+
remote_rule.delete unless rule_exists_in_schedule
|
99
114
|
end
|
115
|
+
end
|
100
116
|
|
101
|
-
|
102
|
-
|
117
|
+
def clear_tasks
|
118
|
+
Task::Rule.fetch(option).each(&:delete)
|
119
|
+
end
|
120
|
+
|
121
|
+
def list_tasks
|
122
|
+
Task::Rule.fetch(option).each do |rule|
|
123
|
+
targets = Task::Target.fetch(option, rule)
|
124
|
+
print_task(targets)
|
103
125
|
end
|
126
|
+
end
|
104
127
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
128
|
+
def print_version
|
129
|
+
puts "Elastic Whenever v#{ElasticWhenever::VERSION}"
|
130
|
+
end
|
131
|
+
|
132
|
+
def print_task(targets)
|
133
|
+
targets.each do |target|
|
134
|
+
puts "#{target.rule.expression} #{target.cluster.name} #{target.definition.name} #{target.container} #{target.commands.join(" ")}"
|
135
|
+
puts
|
110
136
|
end
|
137
|
+
end
|
111
138
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
121
|
-
yield
|
139
|
+
def with_concurrent_modification_handling
|
140
|
+
Retryable.retryable(
|
141
|
+
tries: 5,
|
142
|
+
on: Aws::CloudWatchEvents::Errors::ConcurrentModificationException,
|
143
|
+
sleep: lambda { |_n| rand(1..10) },
|
144
|
+
) do |retries, exn|
|
145
|
+
if retries > 0
|
146
|
+
Logger.instance.warn("concurrent modification detected; Retrying...")
|
122
147
|
end
|
148
|
+
yield
|
123
149
|
end
|
124
150
|
end
|
125
151
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module ElasticWhenever
|
2
2
|
class Option
|
3
|
+
POSSIBLE_RULE_STATES = %w[ENABLED DISABLED].freeze
|
4
|
+
|
3
5
|
DRYRUN_MODE = 1
|
4
6
|
UPDATE_MODE = 2
|
5
7
|
CLEAR_MODE = 3
|
@@ -20,6 +22,11 @@ module ElasticWhenever
|
|
20
22
|
attr_reader :subnets
|
21
23
|
attr_reader :schedule_file
|
22
24
|
attr_reader :iam_role
|
25
|
+
attr_reader :rule_state
|
26
|
+
attr_reader :aws_config
|
27
|
+
attr_reader :ecs_client
|
28
|
+
attr_reader :iam_client
|
29
|
+
attr_reader :cloudwatch_events_client
|
23
30
|
|
24
31
|
class InvalidOptionException < StandardError; end
|
25
32
|
|
@@ -38,13 +45,14 @@ module ElasticWhenever
|
|
38
45
|
@subnets = []
|
39
46
|
@schedule_file = 'config/schedule.rb'
|
40
47
|
@iam_role = 'ecsEventsRole'
|
48
|
+
@rule_state = 'ENABLED'
|
41
49
|
@profile = nil
|
42
50
|
@access_key = nil
|
43
51
|
@secret_key = nil
|
44
52
|
@region = nil
|
45
53
|
|
46
54
|
OptionParser.new do |opts|
|
47
|
-
opts.on('-i', '--update identifier', '
|
55
|
+
opts.on('-i', '--update identifier', 'Creates and deletes tasks as needed by schedule file') do |identifier|
|
48
56
|
@identifier = identifier
|
49
57
|
@mode = UPDATE_MODE
|
50
58
|
end
|
@@ -97,6 +105,9 @@ module ElasticWhenever
|
|
97
105
|
opts.on('--iam-role name', 'IAM role name used by CloudWatch Events. Default: ecsEventsRole') do |role|
|
98
106
|
@iam_role = role
|
99
107
|
end
|
108
|
+
opts.on('--rule-state state', 'The state of the CloudWatch Events Rule. Default: ENABLED') do |state|
|
109
|
+
@rule_state = state
|
110
|
+
end
|
100
111
|
opts.on('--profile profile_name', 'AWS shared profile name') do |profile|
|
101
112
|
@profile = profile
|
102
113
|
end
|
@@ -125,7 +136,19 @@ module ElasticWhenever
|
|
125
136
|
end
|
126
137
|
|
127
138
|
def aws_config
|
128
|
-
{ credentials: credentials, region: region }.delete_if { |_k, v| v.nil? }
|
139
|
+
@aws_config ||= { credentials: credentials, region: region }.delete_if { |_k, v| v.nil? }
|
140
|
+
end
|
141
|
+
|
142
|
+
def ecs_client
|
143
|
+
@ecs_client ||= Aws::ECS::Client.new(aws_config)
|
144
|
+
end
|
145
|
+
|
146
|
+
def iam_client
|
147
|
+
@iam_client ||= Aws::IAM::Client.new(aws_config)
|
148
|
+
end
|
149
|
+
|
150
|
+
def cloudwatch_events_client
|
151
|
+
@cloudwatch_events_client ||= Aws::CloudWatchEvents::Client.new(aws_config)
|
129
152
|
end
|
130
153
|
|
131
154
|
def validate!
|
@@ -133,6 +156,26 @@ module ElasticWhenever
|
|
133
156
|
raise InvalidOptionException.new("You must set cluster") unless cluster
|
134
157
|
raise InvalidOptionException.new("You must set task definition") unless task_definition
|
135
158
|
raise InvalidOptionException.new("You must set container") unless container
|
159
|
+
raise InvalidOptionException.new("Invalid rule state. Possible values are #{POSSIBLE_RULE_STATES.join(", ")}") unless POSSIBLE_RULE_STATES.include?(rule_state)
|
160
|
+
end
|
161
|
+
|
162
|
+
def key
|
163
|
+
Digest::SHA1.hexdigest(
|
164
|
+
[
|
165
|
+
identifier,
|
166
|
+
variables,
|
167
|
+
cluster,
|
168
|
+
task_definition,
|
169
|
+
container,
|
170
|
+
assign_public_ip,
|
171
|
+
launch_type,
|
172
|
+
platform_version,
|
173
|
+
security_groups,
|
174
|
+
subnets,
|
175
|
+
iam_role,
|
176
|
+
rule_state,
|
177
|
+
].join
|
178
|
+
)
|
136
179
|
end
|
137
180
|
|
138
181
|
private
|
@@ -2,7 +2,8 @@ module ElasticWhenever
|
|
2
2
|
class Task
|
3
3
|
class Definition
|
4
4
|
def initialize(option, family)
|
5
|
-
@client =
|
5
|
+
@client = option.ecs_client
|
6
|
+
@family = family
|
6
7
|
@definition = client.describe_task_definition(
|
7
8
|
task_definition: family
|
8
9
|
).task_definition
|
@@ -13,7 +14,12 @@ module ElasticWhenever
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def arn
|
16
|
-
definition&.task_definition_arn
|
17
|
+
arn = definition&.task_definition_arn
|
18
|
+
if family_with_revision?
|
19
|
+
arn
|
20
|
+
else
|
21
|
+
remove_revision(arn)
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
25
|
def containers
|
@@ -24,6 +30,14 @@ module ElasticWhenever
|
|
24
30
|
|
25
31
|
attr_reader :client
|
26
32
|
attr_reader :definition
|
33
|
+
|
34
|
+
def family_with_revision?
|
35
|
+
@family.include?(":")
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove_revision(arn)
|
39
|
+
arn.split(":")[0...-1].join(":")
|
40
|
+
end
|
27
41
|
end
|
28
42
|
end
|
29
43
|
end
|
@@ -2,7 +2,7 @@ module ElasticWhenever
|
|
2
2
|
class Task
|
3
3
|
class Role
|
4
4
|
def initialize(option)
|
5
|
-
client =
|
5
|
+
client = option.iam_client
|
6
6
|
@resource = Aws::IAM::Resource.new(client: client)
|
7
7
|
@role_name = option.iam_role
|
8
8
|
@role = resource.role(@role_name)
|
@@ -1,54 +1,85 @@
|
|
1
1
|
module ElasticWhenever
|
2
2
|
class Task
|
3
3
|
class Rule
|
4
|
+
attr_reader :option
|
4
5
|
attr_reader :name
|
5
6
|
attr_reader :expression
|
7
|
+
attr_reader :description
|
6
8
|
|
7
9
|
class UnsupportedOptionException < StandardError; end
|
8
10
|
|
9
|
-
def self.fetch(option)
|
10
|
-
client =
|
11
|
-
|
12
|
-
|
11
|
+
def self.fetch(option, rules: [], next_token: nil)
|
12
|
+
client = option.cloudwatch_events_client
|
13
|
+
prefix = option.identifier
|
14
|
+
|
15
|
+
response = client.list_rules(name_prefix: prefix, next_token: next_token)
|
16
|
+
response.rules.each do |rule|
|
17
|
+
rules << self.new(
|
13
18
|
option,
|
14
19
|
name: rule.name,
|
15
20
|
expression: rule.schedule_expression,
|
21
|
+
description: rule.description,
|
22
|
+
client: client
|
16
23
|
)
|
17
24
|
end
|
25
|
+
if response.next_token.nil?
|
26
|
+
rules
|
27
|
+
else
|
28
|
+
fetch(option, rules: rules, next_token: response.next_token)
|
29
|
+
end
|
18
30
|
end
|
19
31
|
|
20
|
-
def self.convert(option,
|
32
|
+
def self.convert(option, expression, command)
|
21
33
|
self.new(
|
22
34
|
option,
|
23
|
-
name: rule_name(option
|
24
|
-
expression:
|
35
|
+
name: rule_name(option, expression, command),
|
36
|
+
expression: expression,
|
37
|
+
description: rule_description(option.identifier, expression, command)
|
25
38
|
)
|
26
39
|
end
|
27
40
|
|
28
|
-
def initialize(option, name:, expression:)
|
41
|
+
def initialize(option, name:, expression:, description:, client: nil)
|
42
|
+
@option = option
|
29
43
|
@name = name
|
30
44
|
@expression = expression
|
31
|
-
@
|
45
|
+
@description = description
|
46
|
+
if client != nil
|
47
|
+
@client = client
|
48
|
+
else
|
49
|
+
@client = option.cloudwatch_events_client
|
50
|
+
end
|
32
51
|
end
|
33
52
|
|
34
53
|
def create
|
54
|
+
# See https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutRule.html#API_PutRule_RequestSyntax
|
55
|
+
Logger.instance.message("Creating Rule: #{name} #{expression}")
|
35
56
|
client.put_rule(
|
36
57
|
name: name,
|
37
58
|
schedule_expression: expression,
|
38
|
-
|
59
|
+
description: truncate(description, 512),
|
60
|
+
state: option.rule_state,
|
39
61
|
)
|
40
62
|
end
|
41
63
|
|
42
64
|
def delete
|
43
65
|
targets = client.list_targets_by_rule(rule: name).targets
|
44
66
|
client.remove_targets(rule: name, ids: targets.map(&:id)) unless targets.empty?
|
67
|
+
Logger.instance.message("Removing Rule: #{name}")
|
45
68
|
client.delete_rule(name: name)
|
46
69
|
end
|
47
70
|
|
48
71
|
private
|
49
72
|
|
50
|
-
def self.rule_name(
|
51
|
-
"#{identifier}_#{Digest::SHA1.hexdigest([expression,
|
73
|
+
def self.rule_name(option, expression, command)
|
74
|
+
"#{option.identifier}_#{Digest::SHA1.hexdigest([option.key, expression, command.join("-")].join("-"))}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.rule_description(identifier, expression, command)
|
78
|
+
"#{identifier} - #{expression} - #{command.join(" ")}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def truncate(string, max)
|
82
|
+
string.length > max ? string[0...max] : string
|
52
83
|
end
|
53
84
|
|
54
85
|
attr_reader :client
|
@@ -10,11 +10,12 @@ module ElasticWhenever
|
|
10
10
|
attr_reader :platform_version
|
11
11
|
attr_reader :security_groups
|
12
12
|
attr_reader :subnets
|
13
|
+
attr_reader :rule
|
13
14
|
|
14
15
|
class InvalidContainerException < StandardError; end
|
15
16
|
|
16
17
|
def self.fetch(option, rule)
|
17
|
-
client =
|
18
|
+
client = option.cloudwatch_events_client
|
18
19
|
targets = client.list_targets_by_rule(rule: rule.name).targets
|
19
20
|
targets.map do |target|
|
20
21
|
input = JSON.parse(target.input, symbolize_names: true)
|
@@ -26,7 +27,7 @@ module ElasticWhenever
|
|
26
27
|
container: input[:containerOverrides].first[:name],
|
27
28
|
commands: input[:containerOverrides].first[:command],
|
28
29
|
rule: rule,
|
29
|
-
role: Role.new(option)
|
30
|
+
role: Role.new(option),
|
30
31
|
)
|
31
32
|
end
|
32
33
|
end
|
@@ -47,7 +48,7 @@ module ElasticWhenever
|
|
47
48
|
@platform_version = option.platform_version
|
48
49
|
@security_groups = option.security_groups
|
49
50
|
@subnets = option.subnets
|
50
|
-
@client =
|
51
|
+
@client = option.cloudwatch_events_client
|
51
52
|
end
|
52
53
|
|
53
54
|
def create
|
@@ -102,7 +103,6 @@ module ElasticWhenever
|
|
102
103
|
end
|
103
104
|
end
|
104
105
|
|
105
|
-
attr_reader :rule
|
106
106
|
attr_reader :role
|
107
107
|
attr_reader :client
|
108
108
|
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.
|
4
|
+
version: 0.7.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:
|
11
|
+
date: 2021-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '3.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rexml
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
description: Manage ECS Scheduled Tasks like Whenever
|
126
140
|
email:
|
127
141
|
- watassbass@gmail.com
|
@@ -130,9 +144,9 @@ executables:
|
|
130
144
|
extensions: []
|
131
145
|
extra_rdoc_files: []
|
132
146
|
files:
|
147
|
+
- ".github/workflows/build.yml"
|
133
148
|
- ".gitignore"
|
134
149
|
- ".rspec"
|
135
|
-
- ".travis.yml"
|
136
150
|
- CHANGELOG.md
|
137
151
|
- Gemfile
|
138
152
|
- LICENSE.txt
|
@@ -172,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
186
|
- !ruby/object:Gem::Version
|
173
187
|
version: '0'
|
174
188
|
requirements: []
|
175
|
-
rubygems_version: 3.
|
189
|
+
rubygems_version: 3.1.2
|
176
190
|
signing_key:
|
177
191
|
specification_version: 4
|
178
192
|
summary: Manage ECS Scheduled Tasks like Whenever
|