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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44ac9fe99c9c51c1ee191ab303ce2e5f3d647103544b306c054003b4ecdf1bf6
4
- data.tar.gz: 3877408e457ae24fd0f1f341e6e09ad6603cdf2c67218c651c0b279c4c395ec1
3
+ metadata.gz: faaf28462435852a35046ac97c388f60eaaa3348f9b386251960d83d4351f3a5
4
+ data.tar.gz: c35bf7a5281b25667c3a96dda86920a6b304ba35a147d2421105233f5843fb69
5
5
  SHA512:
6
- metadata.gz: 6921c08998a3e568d522024ee97c277d8d70e0e7e8093e047707f84a3c8bb3b0f6f583111acae84d5ec6eeeba436f8dda1437a97e36fe4b0266261b65e085c8a
7
- data.tar.gz: 5de1bd0c3512ff1dd2cac3192c3894274f6717b93347383610afd2710ae70a1aa0be0d95a7a2753ab69ca35387039799be1afb37fa21ecba5e3c0f9b81f8a5d4
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://travis-ci.org/wata727/elastic_whenever.svg?branch=master)](https://travis-ci.org/wata727/elastic_whenever)
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 as many as `every` block. Each event has as many targets as there are commands in the block.
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 # scheduled task (identifier_68237a3b152a6c44359e1cf5cd8e7cf0def303d7)
71
- rake "hoge:run" # target for `identifier_68237a3b152a6c44359e1cf5cd8e7cf0def303d7`
72
- command "awesome" # target for `identifier_68237a3b152a6c44359e1cf5cd8e7cf0def303d7`
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
- ## Compatible with Whenever
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
 
@@ -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", "~> 10.0"
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
@@ -2,4 +2,4 @@
2
2
 
3
3
  require "elastic_whenever"
4
4
 
5
- exit ElasticWhenever::CLI.run(ARGV)
5
+ exit ElasticWhenever::CLI.new(ARGV).run
@@ -3,123 +3,149 @@ module ElasticWhenever
3
3
  SUCCESS_EXIT_CODE = 0
4
4
  ERROR_EXIT_CODE = 1
5
5
 
6
- class << self
7
- def run(args)
8
- option = Option.new(args)
9
- case option.mode
10
- when Option::DRYRUN_MODE
11
- option.validate!
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
- SUCCESS_EXIT_CODE
35
- rescue Aws::Errors::MissingRegionError
36
- Logger.instance.fail("missing region error occurred; please use `--region` option or export `AWS_REGION` environment variable.")
37
- ERROR_EXIT_CODE
38
- rescue Aws::Errors::MissingCredentialsError
39
- 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.")
40
- ERROR_EXIT_CODE
41
- rescue OptionParser::MissingArgument,
42
- Option::InvalidOptionException,
43
- Task::Target::InvalidContainerException => exn
44
-
45
- Logger.instance.fail(exn.message)
46
- ERROR_EXIT_CODE
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
- private
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
- def update_tasks(option, dry_run:)
52
- schedule = Schedule.new(option.schedule_file, option.verbose, option.variables)
50
+ Logger.instance.fail(exn.message)
51
+ ERROR_EXIT_CODE
52
+ end
53
53
 
54
- cluster = Task::Cluster.new(option, option.cluster)
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
- clear_tasks(option) unless dry_run
62
- schedule.tasks.each do |task|
63
- rule = Task::Rule.convert(option, task)
64
- targets = task.commands.map do |command|
65
- Task::Target.new(
66
- option,
67
- cluster: cluster,
68
- definition: definition,
69
- container: option.container,
70
- commands: command,
71
- rule: rule,
72
- role: role,
73
- )
74
- end
75
-
76
- if dry_run
77
- print_task(rule, targets)
78
- else
79
- begin
80
- rule.create
81
- rescue Aws::CloudWatchEvents::Errors::ValidationException => exn
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
- def clear_tasks(option)
91
- Task::Rule.fetch(option).each(&:delete)
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
- def list_tasks(option)
95
- Task::Rule.fetch(option).each do |rule|
96
- targets = Task::Target.fetch(option, rule)
97
- print_task(rule, targets)
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
- def print_version
102
- puts "Elastic Whenever v#{ElasticWhenever::VERSION}"
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
- def print_task(rule, targets)
106
- targets.each do |target|
107
- puts "#{rule.expression} #{target.cluster.name} #{target.definition.name} #{target.container} #{target.commands.join(" ")}"
108
- puts
109
- end
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
- def with_concurrent_modification_handling
113
- Retryable.retryable(
114
- tries: 5,
115
- on: Aws::CloudWatchEvents::Errors::ConcurrentModificationException,
116
- sleep: lambda { |_n| rand(1..10) },
117
- ) do |retries, exn|
118
- if retries > 0
119
- Logger.instance.warn("concurrent modification detected; Retrying...")
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', 'Clear and create scheduled tasks by schedule file') do |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
@@ -4,7 +4,7 @@ module ElasticWhenever
4
4
  class InvalidInputException < StandardError; end
5
5
 
6
6
  def initialize(option, name)
7
- @client = Aws::ECS::Client.new(option.aws_config)
7
+ @client = option.ecs_client
8
8
  @cluster = client.describe_clusters(
9
9
  clusters: [name]
10
10
  ).clusters.first
@@ -2,7 +2,8 @@ module ElasticWhenever
2
2
  class Task
3
3
  class Definition
4
4
  def initialize(option, family)
5
- @client = Aws::ECS::Client.new(option.aws_config)
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 = Aws::IAM::Client.new(option.aws_config)
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 = Aws::CloudWatchEvents::Client.new(option.aws_config)
11
- client.list_rules(name_prefix: option.identifier).rules.map do |rule|
12
- self.new(
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, task)
32
+ def self.convert(option, expression, command)
21
33
  self.new(
22
34
  option,
23
- name: rule_name(option.identifier, task.expression, task.commands),
24
- expression: task.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
- @client = Aws::CloudWatchEvents::Client.new(option.aws_config)
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
- state: "ENABLED",
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(identifier, expression, commands)
51
- "#{identifier}_#{Digest::SHA1.hexdigest([expression, commands.map { |command| command.join("-") }.join("-")].join("-"))}"
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 = Aws::CloudWatchEvents::Client.new(option.aws_config)
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 = Aws::CloudWatchEvents::Client.new(option.aws_config)
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
@@ -1,3 +1,3 @@
1
1
  module ElasticWhenever
2
- VERSION = "0.5.0"
2
+ VERSION = "0.7.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.5.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: 2019-09-18 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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.0.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
data/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.4.6
5
- - 2.5.5
6
- - 2.6.3
7
- before_install:
8
- - gem update --system
9
- - gem install bundler