lambda_whenever 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/LICENSE +21 -0
- data/README.md +231 -0
- data/Rakefile +12 -0
- data/exe/lambda_whenever_dev +6 -0
- data/lib/lambda_whenever/cli.rb +101 -0
- data/lib/lambda_whenever/event_bridge_scheduler.rb +71 -0
- data/lib/lambda_whenever/iam_role.rb +27 -0
- data/lib/lambda_whenever/logger.rb +23 -0
- data/lib/lambda_whenever/option.rb +123 -0
- data/lib/lambda_whenever/schedule.rb +142 -0
- data/lib/lambda_whenever/target_lambda.rb +34 -0
- data/lib/lambda_whenever/task.rb +35 -0
- data/lib/lambda_whenever/version.rb +5 -0
- data/lib/lambda_whenever/whenever_numeric.rb +40 -0
- data/lib/lambda_whenever.rb +25 -0
- data/sig/lambda_whenever.rbs +4 -0
- metadata +231 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f2de4e951f291bea6c0661284733bc2ecc2f7f2556d0ec2c55af15666cbbe2d6
|
4
|
+
data.tar.gz: 7d8a1abe7a3c1429479dd43f6972bb9f964614192caaaf2d7b9509854827f41e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: db1740b27b33eb0341f5616e31fdb8263ed59670f3a3a3f8ebe304934b4a0e20bbfd60c602014d9f4992f63089e118daa4fcd890f7f94c16a56f0cf01048c4df
|
7
|
+
data.tar.gz: 981e2ec5f416996265692bdd7b3fa7e83cba716e3958ea7d23ec568a5f00c3d02e2a1951354e49139854c81c24629a192450b18767f23fb1c4ac332b72a45a5d
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 toshichanapp
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
# LambdaWhenever
|
2
|
+
|
3
|
+
`lambda_whenever` is a Ruby gem that allows you to create schedules with AWS EventBridge Scheduler targeting Lambda functions using the same syntax as the `whenever` gem.
|
4
|
+
This gem simplifies the management of cron job configurations and enables event-driven batch processing.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'lambda_whenever'
|
12
|
+
```
|
13
|
+
|
14
|
+
Or install it manually with Bundler in your Gemfile:
|
15
|
+
|
16
|
+
```shell
|
17
|
+
$ gem install lambda_whenever
|
18
|
+
```
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
You can use it almost like Whenever. However, please note that you must specify a `--lambda-name` and `--iam-role`.
|
23
|
+
|
24
|
+
```shell
|
25
|
+
$ lambda_whenever --help
|
26
|
+
Usage: lambda_whenever [options]
|
27
|
+
--dryrun dry-run
|
28
|
+
--update Creates and deletes tasks as needed by schedule file
|
29
|
+
-c, --clear Clear scheduled tasks
|
30
|
+
-l, --list List scheduled tasks
|
31
|
+
-v, --version Print version
|
32
|
+
-s, --set variables Example: --set 'environment=staging'
|
33
|
+
--lambda-name name Lambda function name
|
34
|
+
--scheduler-group group_name Optionally specify event bridge scheduler group name
|
35
|
+
-f, --file schedule_file Default: config/schedule.rb
|
36
|
+
--iam-role name IAM role name used by EventBridgeScheduler.
|
37
|
+
--rule-state state The state of the EventBridgeScheduler Rule. Default: ENABLED
|
38
|
+
--region region AWS region
|
39
|
+
-V, --verbose Run rake jobs without --silent
|
40
|
+
```
|
41
|
+
|
42
|
+
### Setting Variables
|
43
|
+
|
44
|
+
Lambda Whenever supports setting variables via the `--set` option, similar to [how Whenever does](https://github.com/javan/whenever/wiki/Setting-variables-on-the-fly).
|
45
|
+
|
46
|
+
Example:
|
47
|
+
|
48
|
+
```shell
|
49
|
+
lambda_whenever --set 'environment=staging&some_var=foo'
|
50
|
+
```
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
if @environment == 'staging'
|
54
|
+
every '0 1 * * *' do
|
55
|
+
rake 'some_task_on_staging'
|
56
|
+
end
|
57
|
+
elsif @some_var == 'foo'
|
58
|
+
every '0 10 * * *' do
|
59
|
+
rake 'some_task'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
The `@environment` variable defaults to `"production"`.
|
65
|
+
|
66
|
+
## How It Works
|
67
|
+
|
68
|
+
Lambda Whenever creates an EventBridge Scheduler schedule for each `every` block. Each schedule can have multiple commands.
|
69
|
+
For example, the following input will generate one schedule with two commands:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
every '0 0 * * *' do
|
73
|
+
rake "hoge:run"
|
74
|
+
command "echo 'you can use raw cron syntax too'"
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
This will result in:
|
79
|
+
|
80
|
+
```shell
|
81
|
+
cron(0 0 * * ? *) { commands: [["bundle", "exec", "rake", "hoge:run", "--silent"], ["echo", "'you", "can", "use", "raw", "cron", "syntax", "too'"]] }
|
82
|
+
```
|
83
|
+
|
84
|
+
In this example, one EventBridge Scheduler schedule is created, containing both the rake task and the command.
|
85
|
+
The scheduled task's name is a digest value calculated from an cron expression, commands, and other parameters.
|
86
|
+
|
87
|
+
## Prerequisites
|
88
|
+
|
89
|
+
Before using this gem, ensure that you have the necessary IAM policies set up for EventBridge to invoke your Lambda functions.
|
90
|
+
|
91
|
+
### IAM Policies for Executing the Gem
|
92
|
+
|
93
|
+
To use this gem, the executing entity (e.g., GitHub Actions, CI/CD pipelines, or other automated systems)
|
94
|
+
must have the necessary IAM policies to register schedules with EventBridge and obtain Lambda ARNs.
|
95
|
+
The following policy grants the required permissions:
|
96
|
+
|
97
|
+
```json
|
98
|
+
{
|
99
|
+
"Version": "2012-10-17",
|
100
|
+
"Statement": [
|
101
|
+
{
|
102
|
+
"Effect": "Allow",
|
103
|
+
"Action": [
|
104
|
+
"scheduler:CreateScheduleGroup",
|
105
|
+
"scheduler:ListSchedules",
|
106
|
+
"scheduler:CreateSchedule",
|
107
|
+
"scheduler:DeleteSchedule",
|
108
|
+
"scheduler:UpdateSchedule",
|
109
|
+
"lambda:ListFunctions",
|
110
|
+
"lambda:GetFunction"
|
111
|
+
],
|
112
|
+
"Resource": "*"
|
113
|
+
}
|
114
|
+
]
|
115
|
+
}
|
116
|
+
```
|
117
|
+
|
118
|
+
### Assume Role Policy for EventBridge
|
119
|
+
|
120
|
+
You need an IAM role that allows EventBridge to assume the role and invoke your Lambda functions.
|
121
|
+
The following policy should be attached to the role:
|
122
|
+
|
123
|
+
```json
|
124
|
+
{
|
125
|
+
"Version": "2012-10-17",
|
126
|
+
"Statement": [
|
127
|
+
{
|
128
|
+
"Effect": "Allow",
|
129
|
+
"Principal": {
|
130
|
+
"Service": "scheduler.amazonaws.com"
|
131
|
+
},
|
132
|
+
"Action": "sts:AssumeRole",
|
133
|
+
"Condition": {
|
134
|
+
"StringEquals": {
|
135
|
+
"aws:SourceAccount": "<your account id>"
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
]
|
140
|
+
}
|
141
|
+
```
|
142
|
+
|
143
|
+
### Execute Lambda Policy
|
144
|
+
|
145
|
+
You also need a policy that allows EventBridge to execute your Lambda functions. Attach the following policy to the role:
|
146
|
+
|
147
|
+
```json
|
148
|
+
{
|
149
|
+
"Version": "2012-10-17",
|
150
|
+
"Statement": [
|
151
|
+
{
|
152
|
+
"Effect": "Allow",
|
153
|
+
"Action": "lambda:InvokeFunction",
|
154
|
+
"Resource": [
|
155
|
+
"<your lambda arn>:*",
|
156
|
+
"<your lambda arn>"
|
157
|
+
]
|
158
|
+
}
|
159
|
+
]
|
160
|
+
}
|
161
|
+
```
|
162
|
+
|
163
|
+
## Compatibility with Whenever
|
164
|
+
|
165
|
+
### Timezone Configuration
|
166
|
+
|
167
|
+
In `lambda_whenever`, setting the timezone is slightly different from the traditional `whenever` gem.
|
168
|
+
Instead of using `env "CRON_TZ", <zone>`, you should use the `set :timezone, <zone>` syntax to specify the timezone for your scheduled tasks.
|
169
|
+
|
170
|
+
Example:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
set :timezone, "Asia/Tokyo"
|
174
|
+
```
|
175
|
+
|
176
|
+
### Methods
|
177
|
+
|
178
|
+
Whenever supports custom job types with `job_type`, `env`, and `job_template` methods, but Lambda Whenever does not support these.
|
179
|
+
|
180
|
+
### mailto
|
181
|
+
|
182
|
+
Whenever supports the `mailto` method, but Lambda Whenever does not.
|
183
|
+
Amazon EventBridge Scheduler does not natively support email notifications.
|
184
|
+
As a result, the `mailto` option is not available in this gem.
|
185
|
+
|
186
|
+
### Frequency
|
187
|
+
|
188
|
+
Lambda Whenever processes the frequency passed to the `every` block similarly to Whenever.
|
189
|
+
|
190
|
+
#### `:reboot`
|
191
|
+
|
192
|
+
Whenever supports `:reboot` as a cron option, but Lambda Whenever does not support it.
|
193
|
+
|
194
|
+
### Bundle Commands
|
195
|
+
|
196
|
+
Whenever checks if the application uses Bundler and automatically adds a prefix to commands.
|
197
|
+
However, Lambda Whenever always adds a prefix, assuming the application is using Bundler.
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
# Whenever
|
201
|
+
# With bundler -> bundle exec rake hoge:run
|
202
|
+
# Without bundler -> rake hoge:run
|
203
|
+
#
|
204
|
+
# Lambda Whenever
|
205
|
+
# bundle exec rake hoge:run
|
206
|
+
#
|
207
|
+
rake "hoge:run"
|
208
|
+
```
|
209
|
+
|
210
|
+
If you don't want to add the prefix, set `bundle_command` to an empty string as follows:
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
set :bundle_command, ""
|
214
|
+
```
|
215
|
+
|
216
|
+
## Development
|
217
|
+
|
218
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
219
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
220
|
+
|
221
|
+
## Contributing
|
222
|
+
|
223
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/toshichanapp/lambda_whenever](https://github.com/toshichanapp/lambda_whenever).
|
224
|
+
|
225
|
+
## License
|
226
|
+
|
227
|
+
The gem is available as open-source under the terms of the MIT License.
|
228
|
+
|
229
|
+
## Acknowledgement
|
230
|
+
|
231
|
+
This gem is inspired by and built upon the work of the [whenever](https://github.com/javan/whenever) and [elastic_whenever](https://github.com/wata727/elastic_whenever) gems. We would like to express our gratitude to the developers and contributors of these projects for their foundational work and contributions to the Ruby community.
|
data/Rakefile
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LambdaWhenever
|
4
|
+
# The CLI class handles command-line interface interactions for the Lambda Whenever tool.
|
5
|
+
class CLI
|
6
|
+
SUCCESS_EXIT_CODE = 0
|
7
|
+
ERROR_EXIT_CODE = 1
|
8
|
+
|
9
|
+
attr_reader :args, :option
|
10
|
+
|
11
|
+
def initialize(args)
|
12
|
+
@args = args
|
13
|
+
@option = Option.new(args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
case option.mode
|
18
|
+
when Option::DRYRUN_MODE
|
19
|
+
option.validate!
|
20
|
+
print_tasks
|
21
|
+
Logger.instance.message("Above is your schedule file converted to scheduled tasks; your scheduled tasks was not updated.")
|
22
|
+
Logger.instance.message("Run `lambda_whenever --help' for more options.")
|
23
|
+
when Option::UPDATE_MODE
|
24
|
+
option.validate!
|
25
|
+
with_concurrent_modification_handling do
|
26
|
+
update_eb_schedules
|
27
|
+
end
|
28
|
+
Logger.instance.log("write", "scheduled tasks updated")
|
29
|
+
when Option::CLEAR_MODE
|
30
|
+
with_concurrent_modification_handling do
|
31
|
+
clear_tasks
|
32
|
+
end
|
33
|
+
Logger.instance.log("write", "scheduled tasks cleared")
|
34
|
+
when Option::LIST_MODE
|
35
|
+
list_tasks
|
36
|
+
Logger.instance.message("Above is your scheduled tasks.")
|
37
|
+
when Option::PRINT_VERSION_MODE
|
38
|
+
print_version
|
39
|
+
end
|
40
|
+
|
41
|
+
SUCCESS_EXIT_CODE
|
42
|
+
rescue Aws::Errors::MissingRegionError
|
43
|
+
Logger.instance.fail("missing region error occurred; please use `--region` option or export `AWS_REGION` environment variable.")
|
44
|
+
ERROR_EXIT_CODE
|
45
|
+
rescue Aws::Errors::MissingCredentialsError
|
46
|
+
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")
|
47
|
+
ERROR_EXIT_CODE
|
48
|
+
rescue OptionParser::MissingArgument,
|
49
|
+
Option::InvalidOptionException => e
|
50
|
+
|
51
|
+
Logger.instance.fail(e.message)
|
52
|
+
ERROR_EXIT_CODE
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def update_eb_schedules
|
58
|
+
schedule = Schedule.new(option.schedule_file, option.verbose, option.variables)
|
59
|
+
scheduler = EventBridgeScheduler.new(option.scheduler_client, schedule.timezone)
|
60
|
+
scheduler.create_schedule_group(option.scheduler_group)
|
61
|
+
scheduler.clean_up_schedules(option.scheduler_group)
|
62
|
+
|
63
|
+
lambda_arn = TargetLambda.fetch_arn(option.lambda_name, option.lambda_client)
|
64
|
+
schedule.tasks.map do |task|
|
65
|
+
target = TargetLambda.new(arn: lambda_arn, task: task)
|
66
|
+
|
67
|
+
scheduler.create_schedule(target, option)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def clear_tasks
|
72
|
+
scheduler = EventBridgeScheduler.new(option.scheduler_client)
|
73
|
+
scheduler.clean_up_schedules(option.scheduler_group)
|
74
|
+
end
|
75
|
+
|
76
|
+
def list_tasks
|
77
|
+
scheduler = EventBridgeScheduler.new(option.scheduler_client)
|
78
|
+
scheduler.list_schedules(option.scheduler_group)
|
79
|
+
end
|
80
|
+
|
81
|
+
def print_version
|
82
|
+
puts "Lambda Whenever v#{LambdaWhenever::VERSION}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def print_tasks
|
86
|
+
schedule = Schedule.new(option.schedule_file, option.verbose, option.variables)
|
87
|
+
schedule.print_tasks
|
88
|
+
end
|
89
|
+
|
90
|
+
def with_concurrent_modification_handling
|
91
|
+
Retryable.retryable(
|
92
|
+
tries: 5,
|
93
|
+
on: Aws::Scheduler::Errors::ConflictException,
|
94
|
+
sleep: ->(_n) { rand(1..10) }
|
95
|
+
) do |retries, _exn|
|
96
|
+
Logger.instance.warn("concurrent modification detected; Retrying...") if retries.positive?
|
97
|
+
yield
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LambdaWhenever
|
4
|
+
# The EventBridgeScheduler class is responsible for managing schedules in AWS EventBridge.
|
5
|
+
class EventBridgeScheduler
|
6
|
+
attr_reader :timezone
|
7
|
+
|
8
|
+
def initialize(client, timezone = "UTC")
|
9
|
+
@scheduler_client = client
|
10
|
+
@timezone = timezone
|
11
|
+
end
|
12
|
+
|
13
|
+
def list_schedules(group_name)
|
14
|
+
Logger.instance.message("Schedules in group '#{group_name}':")
|
15
|
+
response = @scheduler_client.list_schedules({ group_name: group_name })
|
16
|
+
response.schedules.each do |schedule|
|
17
|
+
detail = @scheduler_client.get_schedule({ group_name: group_name, name: schedule.name })
|
18
|
+
puts "#{schedule.state} #{schedule.name} #{detail.schedule_expression} #{detail.description}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_schedule_group(group_name)
|
23
|
+
@scheduler_client.create_schedule_group({ name: group_name })
|
24
|
+
puts "Schedule group '#{group_name}' created."
|
25
|
+
rescue Aws::Scheduler::Errors::ConflictException
|
26
|
+
puts "Schedule group '#{group_name}' already exists."
|
27
|
+
end
|
28
|
+
|
29
|
+
# https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Scheduler/Client.html#create_schedule-instance_method
|
30
|
+
def create_schedule(target, option)
|
31
|
+
task = target.task
|
32
|
+
@scheduler_client.create_schedule({
|
33
|
+
name: schedule_name(task, option),
|
34
|
+
schedule_expression: task.expression,
|
35
|
+
schedule_expression_timezone: timezone,
|
36
|
+
flexible_time_window: {
|
37
|
+
maximum_window_in_minutes: 5,
|
38
|
+
mode: "FLEXIBLE"
|
39
|
+
},
|
40
|
+
target: {
|
41
|
+
arn: target.arn,
|
42
|
+
role_arn: IamRole.new(option).arn,
|
43
|
+
input: target.input
|
44
|
+
},
|
45
|
+
group_name: option.scheduler_group,
|
46
|
+
state: option.rule_state,
|
47
|
+
description: schedule_description(task)
|
48
|
+
})
|
49
|
+
end
|
50
|
+
|
51
|
+
def schedule_name(task, option)
|
52
|
+
Digest::SHA1.hexdigest([option.key, task.expression, *task.commands].join("-")).to_s[0, 64]
|
53
|
+
end
|
54
|
+
|
55
|
+
def schedule_description(task)
|
56
|
+
task.commands.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def clean_up_schedules(schedule_group)
|
60
|
+
response = @scheduler_client.list_schedules({ group_name: schedule_group })
|
61
|
+
response.schedules.each do |schedule|
|
62
|
+
@scheduler_client.delete_schedule({
|
63
|
+
name: schedule.name,
|
64
|
+
group_name: schedule_group
|
65
|
+
})
|
66
|
+
end
|
67
|
+
rescue Aws::Scheduler::Errors::ResourceNotFoundException
|
68
|
+
puts "Schedule group '#{schedule_group}' does not exist."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LambdaWhenever
|
4
|
+
# The IamRole class is responsible for interacting with AWS IAM roles.
|
5
|
+
class IamRole
|
6
|
+
def initialize(option)
|
7
|
+
client = option.iam_client
|
8
|
+
@resource = Aws::IAM::Resource.new(client: client)
|
9
|
+
@role_name = option.iam_role
|
10
|
+
@role = resource.role(@role_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def arn
|
14
|
+
role&.arn
|
15
|
+
end
|
16
|
+
|
17
|
+
def exists?
|
18
|
+
!!arn
|
19
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :resource, :role
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LambdaWhenever
|
4
|
+
class Logger
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def fail(message)
|
8
|
+
Kernel.warn "[fail] #{message}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def warn(message)
|
12
|
+
Kernel.warn "[warn] #{message}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def log(event, message)
|
16
|
+
puts "[#{event}] #{message}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def message(message)
|
20
|
+
puts "## [message] #{message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LambdaWhenever
|
4
|
+
# The Option class handles parsing and validation of command-line options.
|
5
|
+
class Option
|
6
|
+
POSSIBLE_RULE_STATES = %w[ENABLED DISABLED].freeze
|
7
|
+
|
8
|
+
DRYRUN_MODE = 1
|
9
|
+
UPDATE_MODE = 2
|
10
|
+
CLEAR_MODE = 3
|
11
|
+
LIST_MODE = 4
|
12
|
+
PRINT_VERSION_MODE = 5
|
13
|
+
|
14
|
+
attr_reader :mode, :verbose, :variables, :schedule_file, :iam_role, :rule_state,
|
15
|
+
:lambda_name, :scheduler_group
|
16
|
+
|
17
|
+
class InvalidOptionException < StandardError; end
|
18
|
+
|
19
|
+
def initialize(args)
|
20
|
+
@mode = DRYRUN_MODE
|
21
|
+
@verbose = false
|
22
|
+
@variables = []
|
23
|
+
@schedule_file = "config/schedule.rb"
|
24
|
+
@iam_role = nil
|
25
|
+
@rule_state = "ENABLED"
|
26
|
+
@lambda_name = nil
|
27
|
+
@scheduler_group = "lambda-whenever-dev-group"
|
28
|
+
@region = nil
|
29
|
+
|
30
|
+
OptionParser.new do |opts|
|
31
|
+
opts.on("--dryrun", "dry-run") do
|
32
|
+
@mode = DRYRUN_MODE
|
33
|
+
end
|
34
|
+
opts.on("--update", "Creates and deletes tasks as needed by schedule file") do
|
35
|
+
@mode = UPDATE_MODE
|
36
|
+
end
|
37
|
+
opts.on("-c", "--clear", "Clear scheduled tasks") do
|
38
|
+
@mode = CLEAR_MODE
|
39
|
+
end
|
40
|
+
opts.on("-l", "--list", "List scheduled tasks") do
|
41
|
+
@mode = LIST_MODE
|
42
|
+
end
|
43
|
+
opts.on("-v", "--version", "Print version") do
|
44
|
+
@mode = PRINT_VERSION_MODE
|
45
|
+
end
|
46
|
+
opts.on("-s", "--set variables", "Example: --set 'environment=staging'") do |set|
|
47
|
+
pairs = set.split("&")
|
48
|
+
pairs.each do |pair|
|
49
|
+
unless pair.include?("=")
|
50
|
+
Logger.instance.warn("Ignore variable set: #{pair}")
|
51
|
+
next
|
52
|
+
end
|
53
|
+
key, value = pair.split("=")
|
54
|
+
@variables << { key: key, value: value }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
opts.on("--lambda-name name", "Lambda function name") do |name|
|
58
|
+
@lambda_name = name
|
59
|
+
end
|
60
|
+
opts.on("--scheduler-group group_name",
|
61
|
+
"Optionally specify event bridge scheduler group name") do |group|
|
62
|
+
@scheduler_group = group
|
63
|
+
end
|
64
|
+
opts.on("-f", "--file schedule_file", "Default: config/schedule.rb") do |file|
|
65
|
+
@schedule_file = file
|
66
|
+
end
|
67
|
+
opts.on("--iam-role name", "IAM role name used by EventBridgeScheduler.") do |role|
|
68
|
+
@iam_role = role
|
69
|
+
end
|
70
|
+
opts.on("--rule-state state", "The state of the EventBridgeScheduler Rule. Default: ENABLED") do |state|
|
71
|
+
@rule_state = state
|
72
|
+
end
|
73
|
+
opts.on("--region region", "AWS region") do |region|
|
74
|
+
@region = region
|
75
|
+
end
|
76
|
+
opts.on("-V", "--verbose", "Run rake jobs without --silent") do
|
77
|
+
@verbose = true
|
78
|
+
end
|
79
|
+
end.parse(args)
|
80
|
+
end
|
81
|
+
|
82
|
+
def validate!
|
83
|
+
raise InvalidOptionException, "Can't find file: #{schedule_file}" unless File.exist?(schedule_file)
|
84
|
+
raise InvalidOptionException, "You must set lambda-name" unless lambda_name
|
85
|
+
raise InvalidOptionException, "You must set iam-role" unless iam_role
|
86
|
+
return if POSSIBLE_RULE_STATES.include?(rule_state)
|
87
|
+
|
88
|
+
raise InvalidOptionException, "Invalid rule state. Possible values are #{POSSIBLE_RULE_STATES.join(", ")}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def aws_config
|
92
|
+
@aws_config ||= { region: region }.delete_if { |_k, v| v.nil? }
|
93
|
+
end
|
94
|
+
|
95
|
+
def iam_client
|
96
|
+
@iam_client ||= Aws::IAM::Client.new(aws_config)
|
97
|
+
end
|
98
|
+
|
99
|
+
def lambda_client
|
100
|
+
@lambda_client ||= Aws::Lambda::Client.new(aws_config)
|
101
|
+
end
|
102
|
+
|
103
|
+
def scheduler_client
|
104
|
+
@scheduler_client ||= Aws::Scheduler::Client.new(aws_config)
|
105
|
+
end
|
106
|
+
|
107
|
+
def key
|
108
|
+
Digest::SHA1.hexdigest(
|
109
|
+
[
|
110
|
+
variables,
|
111
|
+
iam_role,
|
112
|
+
rule_state,
|
113
|
+
lambda_name,
|
114
|
+
scheduler_group
|
115
|
+
].join
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
attr_reader :region
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "whenever_numeric"
|
4
|
+
|
5
|
+
module LambdaWhenever
|
6
|
+
class Schedule
|
7
|
+
attr_reader :tasks, :chronic_options, :bundle_command, :environment, :timezone
|
8
|
+
|
9
|
+
class UnsupportedFrequencyException < StandardError; end
|
10
|
+
|
11
|
+
using WheneverNumeric
|
12
|
+
|
13
|
+
def initialize(file, verbose, variables)
|
14
|
+
@environment = "production"
|
15
|
+
@verbose = verbose
|
16
|
+
@tasks = []
|
17
|
+
@chronic_options = {}
|
18
|
+
@bundle_command = "bundle exec"
|
19
|
+
|
20
|
+
variables.each { |var| set(var[:key], var[:value]) }
|
21
|
+
instance_eval(File.read(file), file)
|
22
|
+
@timezone ||= "UTC"
|
23
|
+
end
|
24
|
+
|
25
|
+
def set(key, value)
|
26
|
+
instance_variable_set("@#{key}", value) unless key == "tasks"
|
27
|
+
end
|
28
|
+
|
29
|
+
def every(frequency, options = {}, &block)
|
30
|
+
expressions = schedule_expressions(frequency, options)
|
31
|
+
tasks = expressions.map do |expression|
|
32
|
+
Task.new(@environment, @verbose, @bundle_command, expression).tap do |task|
|
33
|
+
task.instance_eval(&block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@tasks.concat tasks
|
37
|
+
rescue UnsupportedFrequencyException => e
|
38
|
+
Logger.instance.warn(e.message)
|
39
|
+
end
|
40
|
+
|
41
|
+
def schedule_expressions(frequency, options)
|
42
|
+
tmp_expression = expression_by_frequency(frequency, options)
|
43
|
+
return ["cron(#{tmp_expression.join(" ")})"] unless options[:at].is_a?(Array)
|
44
|
+
|
45
|
+
times = options[:at]
|
46
|
+
grouped_times = times.group_by { |time| time[/\d\d?:(\d\d?)/, 1] }
|
47
|
+
|
48
|
+
grouped_times.map do |minute, hour_list|
|
49
|
+
hours = hour_list.map { |time| time[/^\d{1,2}/] }.join(",")
|
50
|
+
_, __, *rest = tmp_expression
|
51
|
+
exp = [minute, hours, *rest]
|
52
|
+
"cron(#{exp.join(" ")})"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# index minutes: 0, hours: 1, day_of_month: 2, month: 3, day_of_week: 4, year: 5
|
57
|
+
def expression_by_frequency(frequency, options)
|
58
|
+
opts = { now: Time.new(2017, 1, 1, 0, 0, 0) }.merge(@chronic_options)
|
59
|
+
time = Chronic.parse(options[:at], opts) || Time.new(2017, 1, 1, 0, 0, 0)
|
60
|
+
|
61
|
+
case frequency
|
62
|
+
when 1.minute
|
63
|
+
["*", "*", "*", "*", "?", "*"]
|
64
|
+
when :hour, 1.hour
|
65
|
+
[time.min.to_s, "*", "*", "*", "?", "*"]
|
66
|
+
when :day, 1.day
|
67
|
+
[time.min.to_s, time.hour.to_s, "*", "*", "?", "*"]
|
68
|
+
when :month, 1.month
|
69
|
+
[time.min.to_s, time.hour.to_s, time.day, "*", "?", "*"]
|
70
|
+
when :year, 1.year
|
71
|
+
[time.min.to_s, time.hour.to_s, time.day, time.month, "?", "*"]
|
72
|
+
when :sunday
|
73
|
+
[time.min.to_s, time.hour.to_s, "?", "*", "SUN", "*"]
|
74
|
+
when :monday
|
75
|
+
[time.min.to_s, time.hour.to_s, "?", "*", "MON", "*"]
|
76
|
+
when :tuesday
|
77
|
+
[time.min.to_s, time.hour.to_s, "?", "*", "TUE", "*"]
|
78
|
+
when :wednesday
|
79
|
+
[time.min.to_s, time.hour.to_s, "?", "*", "WED", "*"]
|
80
|
+
when :thursday
|
81
|
+
[time.min.to_s, time.hour.to_s, "?", "*", "THU", "*"]
|
82
|
+
when :friday
|
83
|
+
[time.min.to_s, time.hour.to_s, "?", "*", "FRI", "*"]
|
84
|
+
when :saturday
|
85
|
+
[time.min.to_s, time.hour.to_s, "?", "*", "SAT", "*"]
|
86
|
+
when :weekend
|
87
|
+
[time.min.to_s, time.hour.to_s, "?", "*", "SUN,SAT", "*"]
|
88
|
+
when :weekday
|
89
|
+
[time.min.to_s, time.hour.to_s, "?", "*", "MON-FRI", "*"]
|
90
|
+
when 1.second...1.minute
|
91
|
+
raise UnsupportedFrequencyException, "Time must be in minutes or higher. Ignore this task."
|
92
|
+
when 1.minute...1.hour
|
93
|
+
step = (frequency / 60).round
|
94
|
+
min = []
|
95
|
+
((60 % step).zero? ? 0 : step).step(59, step) { |i| min << i }
|
96
|
+
[min.join(","), "*", "*", "*", "?", "*"]
|
97
|
+
when 1.hour...1.day
|
98
|
+
step = (frequency / 60 / 60).round
|
99
|
+
hour = []
|
100
|
+
((24 % step).zero? ? 0 : step).step(23, step) { |i| hour << i }
|
101
|
+
[time.min.to_s, hour.join(","), "*", "*", "?", "*"]
|
102
|
+
when 1.day...1.month
|
103
|
+
step = (frequency / 24 / 60 / 60).round
|
104
|
+
day = []
|
105
|
+
(step <= 16 ? 1 : step).step(30, step) { |i| day << i }
|
106
|
+
[time.min.to_s, time.hour.to_s, day.join(","), "*", "?", "*"]
|
107
|
+
when 1.month...12.months
|
108
|
+
step = (frequency / 30 / 24 / 60 / 60).round
|
109
|
+
month = []
|
110
|
+
(step <= 6 ? 1 : step).step(12, step) { |i| month << i }
|
111
|
+
[time.min.to_s, time.hour.to_s, time.day, month.join(","), "?", "*"]
|
112
|
+
when 12.months...Float::INFINITY
|
113
|
+
raise UnsupportedFrequencyException, "Time must be in months or lower. Ignore this task."
|
114
|
+
when %r{^((\*?[\d/,-]*)\s*){5}$}
|
115
|
+
min, hour, day, mon, week, year = frequency.split(" ")
|
116
|
+
# You can't specify the Day-of-month and Day-of-week fields in the same Cron expression.
|
117
|
+
# If you specify a value in one of the fields, you must use a ? (question mark) in the other.
|
118
|
+
week.gsub!("*", "?") if day != "?"
|
119
|
+
day.gsub!("*", "?") if week != "?"
|
120
|
+
# cron syntax: sunday -> 0
|
121
|
+
# scheduled expression: sunday -> 1
|
122
|
+
week.gsub!(/(\d)/) { |match| Integer(match) + 1 }
|
123
|
+
year ||= "*"
|
124
|
+
[min, hour, day, mon, week, year]
|
125
|
+
when %r{^((\*?\??L?W?[\d/,-]*)\s*){6}$}
|
126
|
+
frequency.split(" ")
|
127
|
+
else
|
128
|
+
raise UnsupportedFrequencyException, "`#{frequency}` is not supported option. Ignore this task."
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def print_tasks
|
133
|
+
@tasks.each do |task|
|
134
|
+
puts "#{task.expression} { commands: #{task.commands} }"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def method_missing(name, *_args)
|
139
|
+
Logger.instance.warn("Skipping unsupported method: #{name}")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LambdaWhenever
|
4
|
+
# The TargetLambda class represents a Lambda function as a target for scheduling.
|
5
|
+
class TargetLambda
|
6
|
+
attr_reader :role_arn, :arn, :input, :name, :task
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def fetch_arn(function_name, client)
|
10
|
+
response = client.get_function({
|
11
|
+
function_name: function_name
|
12
|
+
})
|
13
|
+
response.configuration.function_arn
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(arn:, task:)
|
18
|
+
@arn = arn
|
19
|
+
@task = task
|
20
|
+
@input = input_json
|
21
|
+
end
|
22
|
+
|
23
|
+
# https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-schedule-context-attributes.html
|
24
|
+
def input_json
|
25
|
+
{
|
26
|
+
execution_id: "<aws.scheduler.execution-id>",
|
27
|
+
scheduled_time: "<aws.scheduler.scheduled-time>",
|
28
|
+
schedule_arn: "<aws.scheduler.schedule-arn>",
|
29
|
+
attempt_number: "<aws.scheduler.attempt-number>",
|
30
|
+
commands: task.commands
|
31
|
+
}.to_json
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LambdaWhenever
|
4
|
+
class Task
|
5
|
+
attr_reader :commands, :expression
|
6
|
+
|
7
|
+
def initialize(environment, verbose, bundle_command, expression)
|
8
|
+
@environment = environment
|
9
|
+
@verbose_mode = verbose ? nil : "--silent"
|
10
|
+
@bundle_command = bundle_command.split(" ")
|
11
|
+
@expression = expression
|
12
|
+
@commands = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def command(task)
|
16
|
+
@commands << task.split(" ")
|
17
|
+
end
|
18
|
+
|
19
|
+
def rake(task)
|
20
|
+
@commands << [@bundle_command, "rake", task, @verbose_mode].flatten.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
def runner(src)
|
24
|
+
@commands << [@bundle_command, "rails", "runner", "-e", @environment, src].flatten
|
25
|
+
end
|
26
|
+
|
27
|
+
def script(script)
|
28
|
+
@commands << [@bundle_command, "script/#{script}"].flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(name, *_args)
|
32
|
+
Logger.instance.warn("Skipping unsupported method: #{name}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WheneverNumeric
|
4
|
+
refine Numeric do
|
5
|
+
def seconds
|
6
|
+
self
|
7
|
+
end
|
8
|
+
alias_method :second, :seconds
|
9
|
+
|
10
|
+
def minutes
|
11
|
+
self * 60
|
12
|
+
end
|
13
|
+
alias_method :minute, :minutes
|
14
|
+
|
15
|
+
def hours
|
16
|
+
(self * 60).minutes
|
17
|
+
end
|
18
|
+
alias_method :hour, :hours
|
19
|
+
|
20
|
+
def days
|
21
|
+
(self * 24).hours
|
22
|
+
end
|
23
|
+
alias_method :day, :days
|
24
|
+
|
25
|
+
def weeks
|
26
|
+
(self * 7).days
|
27
|
+
end
|
28
|
+
alias_method :week, :weeks
|
29
|
+
|
30
|
+
def months
|
31
|
+
(self * 30).days
|
32
|
+
end
|
33
|
+
alias_method :month, :months
|
34
|
+
|
35
|
+
def years
|
36
|
+
(self * 365.25).days
|
37
|
+
end
|
38
|
+
alias_method :year, :years
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require "aws-sdk-iam"
|
5
|
+
require "aws-sdk-scheduler"
|
6
|
+
require "aws-sdk-lambda"
|
7
|
+
require "chronic"
|
8
|
+
require "singleton"
|
9
|
+
require "json"
|
10
|
+
require "retryable"
|
11
|
+
|
12
|
+
require "lambda_whenever/version"
|
13
|
+
require "lambda_whenever/cli"
|
14
|
+
require "lambda_whenever/logger"
|
15
|
+
require "lambda_whenever/option"
|
16
|
+
require "lambda_whenever/schedule"
|
17
|
+
require "lambda_whenever/event_bridge_scheduler"
|
18
|
+
require "lambda_whenever/task"
|
19
|
+
require "lambda_whenever/iam_role"
|
20
|
+
require "lambda_whenever/target_lambda"
|
21
|
+
|
22
|
+
module LambdaWhenever
|
23
|
+
class Error < StandardError; end
|
24
|
+
# Your code goes here...
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lambda_whenever
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- toshichanapp
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-09-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '13.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '13.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.21'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.21'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.21'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.21'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: aws-sdk-iam
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: aws-sdk-lambda
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: aws-sdk-scheduler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: base64
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.2'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.2'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: bigdecimal
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.1'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.1'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: chronic
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.10'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.10'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: retryable
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '3.0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '3.0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rexml
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
description: whenever for Amazon EventBridge Scheduler.
|
182
|
+
email:
|
183
|
+
- toshichanapp@gmail.com
|
184
|
+
executables:
|
185
|
+
- lambda_whenever_dev
|
186
|
+
extensions: []
|
187
|
+
extra_rdoc_files: []
|
188
|
+
files:
|
189
|
+
- ".rspec"
|
190
|
+
- ".rubocop.yml"
|
191
|
+
- LICENSE
|
192
|
+
- README.md
|
193
|
+
- Rakefile
|
194
|
+
- exe/lambda_whenever_dev
|
195
|
+
- lib/lambda_whenever.rb
|
196
|
+
- lib/lambda_whenever/cli.rb
|
197
|
+
- lib/lambda_whenever/event_bridge_scheduler.rb
|
198
|
+
- lib/lambda_whenever/iam_role.rb
|
199
|
+
- lib/lambda_whenever/logger.rb
|
200
|
+
- lib/lambda_whenever/option.rb
|
201
|
+
- lib/lambda_whenever/schedule.rb
|
202
|
+
- lib/lambda_whenever/target_lambda.rb
|
203
|
+
- lib/lambda_whenever/task.rb
|
204
|
+
- lib/lambda_whenever/version.rb
|
205
|
+
- lib/lambda_whenever/whenever_numeric.rb
|
206
|
+
- sig/lambda_whenever.rbs
|
207
|
+
homepage: https://github.com/toshichanapp/lambda_whenever
|
208
|
+
licenses: []
|
209
|
+
metadata:
|
210
|
+
homepage_uri: https://github.com/toshichanapp/lambda_whenever
|
211
|
+
source_code_uri: https://github.com/toshichanapp/lambda_whenever
|
212
|
+
post_install_message:
|
213
|
+
rdoc_options: []
|
214
|
+
require_paths:
|
215
|
+
- lib
|
216
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
217
|
+
requirements:
|
218
|
+
- - ">="
|
219
|
+
- !ruby/object:Gem::Version
|
220
|
+
version: 3.0.0
|
221
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
222
|
+
requirements:
|
223
|
+
- - ">="
|
224
|
+
- !ruby/object:Gem::Version
|
225
|
+
version: '0'
|
226
|
+
requirements: []
|
227
|
+
rubygems_version: 3.5.11
|
228
|
+
signing_key:
|
229
|
+
specification_version: 4
|
230
|
+
summary: whenever for Amazon EventBridge Scheduler.
|
231
|
+
test_files: []
|