seijaku 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +22 -1
- data/bin/seijaku +33 -8
- data/docs/4 - scheduler.md +54 -0
- data/lib/seijaku/payload.rb +2 -0
- data/lib/seijaku/scheduler.rb +48 -0
- data/lib/seijaku/scheduler_executor.rb +31 -0
- data/lib/seijaku/step.rb +1 -1
- data/lib/seijaku/version.rb +1 -1
- data/lib/seijaku.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 638106c6524b04b68139b67af16ff6b1f66d74fa82b05d15be1b2c26f42e5684
|
4
|
+
data.tar.gz: 40b3f7a10c0575df85225a04ebc040c5d6d09521ebc789148a6f888319e9ec31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4387d2f340c6d3552f6ad91529ae5600e94ce1fd02e49556fef4e8bd1a125b4a078d5fcaca099a5ca766116db8481e08e1889214b92d8422bac1f14ed01ba646
|
7
|
+
data.tar.gz: 2bd4543e36d3ae4abbeaf8317995c2a689be7fd1ae0409f1b3ed34842ac58779802577399116608242979153d92f271df7db663aa999baa694ae1fbc3dd56c42
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Seijaku
|
2
2
|
|
3
|
+
[![Seijaku version](https://badge.fury.io/rb/seijaku.svg)](https://badge.fury.io/rb/seijaku)
|
4
|
+
|
3
5
|
Seijaku is a program that allows you to execute shell commands listed in YAML payload files, ensuring that they are executed correctly.
|
6
|
+
It includes a lightweight scheduler that will take care of executing payloads with specific delay between two executions.
|
4
7
|
|
5
8
|
## Concepts
|
6
9
|
|
@@ -12,6 +15,8 @@ Each task can have "pre" and "post" tasks, for example to create and delete fold
|
|
12
15
|
|
13
16
|
A step sometimes needs variables in order to be performed correctly: Seijaku supports the direct definition of variables or from an environment variable of the shell running Seijaku.
|
14
17
|
|
18
|
+
A scheduler is a file listing payloads that must be executed with unique specifications: a name, a delay between two runs and the path to the concerned payload.
|
19
|
+
|
15
20
|
## Example
|
16
21
|
|
17
22
|
```yaml
|
@@ -49,6 +54,21 @@ tasks:
|
|
49
54
|
- ssh: echo "executed on host"
|
50
55
|
```
|
51
56
|
|
57
|
+
### Scheduler
|
58
|
+
|
59
|
+
```yaml
|
60
|
+
name: my-scheduler
|
61
|
+
|
62
|
+
payloads:
|
63
|
+
- payload: ./test/my-payload.yaml
|
64
|
+
name: My test Payload
|
65
|
+
every: 3600 # executed every hour
|
66
|
+
|
67
|
+
- payload: ./test/another-payload.yaml
|
68
|
+
name: Another payload
|
69
|
+
every: 60 # executed every minute
|
70
|
+
```
|
71
|
+
|
52
72
|
## Installation
|
53
73
|
|
54
74
|
### Dependencies
|
@@ -66,5 +86,6 @@ gem install seijaku
|
|
66
86
|
|
67
87
|
```bash
|
68
88
|
seijaku -h
|
69
|
-
seijaku -f ./my-payload.yaml
|
89
|
+
seijaku -f ./my-payload.yaml # one-time payload execution
|
90
|
+
seijaku -s ./my-scheduler.yaml # recurrent payloads execution with delay
|
70
91
|
```
|
data/bin/seijaku
CHANGED
@@ -19,6 +19,7 @@ module App
|
|
19
19
|
opts = OptionParser.new
|
20
20
|
opts.banner = "Seijaku: simply runs YAML tasks with shell"
|
21
21
|
opts.on("-f", "--file FILE", "Payload file path") { |o| options[:payload] = o }
|
22
|
+
opts.on("-s", "--scheduler FILE", "Scheduler file") { |o| options[:scheduler] = o }
|
22
23
|
opts.on("-h", "--help", "Shows help and exit") do
|
23
24
|
puts opts
|
24
25
|
exit(0)
|
@@ -26,17 +27,41 @@ module App
|
|
26
27
|
|
27
28
|
opts.parse!
|
28
29
|
|
29
|
-
if options[:payload].nil?
|
30
|
+
if options[:payload].nil? and options[:scheduler].nil?
|
30
31
|
puts opts
|
31
|
-
|
32
|
+
puts "Either -f or -s must be set"
|
33
|
+
exit 1
|
32
34
|
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
36
|
+
if options[:payload] and options[:scheduler]
|
37
|
+
puts opts
|
38
|
+
puts "Either -f or -s must be set"
|
39
|
+
exit 1
|
40
|
+
end
|
37
41
|
|
38
|
-
|
42
|
+
if options[:payload]
|
43
|
+
payload_file = YAML.safe_load(
|
44
|
+
File.read(options[:payload])
|
45
|
+
)
|
39
46
|
|
40
|
-
|
41
|
-
|
47
|
+
logger.info "Starting Seijaku. Payload: #{payload_file["name"]}"
|
48
|
+
|
49
|
+
payload = Payload.new(payload_file, logger)
|
50
|
+
payload.execute!
|
51
|
+
end
|
52
|
+
|
53
|
+
if options[:scheduler]
|
54
|
+
scheduler_file = YAML.safe_load(
|
55
|
+
File.read(options[:scheduler])
|
56
|
+
)
|
57
|
+
|
58
|
+
logger.info "Starting Seijaku. Scheduler: #{scheduler_file["name"]}"
|
59
|
+
begin
|
60
|
+
scheduler = Scheduler.new(scheduler_file, logger)
|
61
|
+
scheduler.monitor!
|
62
|
+
rescue Interrupt
|
63
|
+
scheduler.soft_exit!
|
64
|
+
puts "All payloads did stop their execution gracefully."
|
65
|
+
end
|
66
|
+
end
|
42
67
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Schedulers
|
2
|
+
|
3
|
+
Since version `0.3.0`, Seijaku implements a task scheduler that permits us to execute payloads with interval between two runs.
|
4
|
+
|
5
|
+
Instead of using the `-f` parameter to run a payload, you can use the `-s (--scheduler)` parameter to pass a `schedulerfile` to Seijaku.
|
6
|
+
|
7
|
+
## How it works ?
|
8
|
+
|
9
|
+
Ruby isn't great at parallelism, but it does integrate `Threads`. The Scheduler will assign a Thread to all the payloads to execute.
|
10
|
+
|
11
|
+
## Example
|
12
|
+
|
13
|
+
Here is a really basic payload for example purpose:
|
14
|
+
|
15
|
+
```yaml
|
16
|
+
name: my-payload
|
17
|
+
tasks:
|
18
|
+
- name: say hello world
|
19
|
+
steps:
|
20
|
+
- sh: echo "hello world"
|
21
|
+
```
|
22
|
+
|
23
|
+
If you want to run this payload every 10 seconds, you can create a scheduler file (call it whatever you want), describing the awaited behavior.
|
24
|
+
|
25
|
+
```yaml
|
26
|
+
name: "my-scheduler"
|
27
|
+
|
28
|
+
payloads:
|
29
|
+
- payload: ./my-payload.yaml
|
30
|
+
every: 10
|
31
|
+
name: say-hello-world-10-s
|
32
|
+
```
|
33
|
+
|
34
|
+
## Graceful stop
|
35
|
+
|
36
|
+
CTRL+C (Interrupt signal) sends an information to all the SchedulerExecutors running, asking them to do not re-schedule the payload execution they are in charge of once the current execution is finished.
|
37
|
+
|
38
|
+
```
|
39
|
+
...
|
40
|
+
> CTRL+C
|
41
|
+
[2023-12-14T18:48:12.509483 #8841] INFO -- : Soft exit asked by user, waiting for payloads to stop naturally...
|
42
|
+
I, [2023-12-14T18:48:12.509789 #8841] INFO -- : test payload: still waiting...
|
43
|
+
I, [2023-12-14T18:48:12.509856 #8841] INFO -- : another-payload: still waiting...
|
44
|
+
I, [2023-12-14T18:48:13.515211 #8841] INFO -- : test payload: still waiting...
|
45
|
+
I, [2023-12-14T18:48:13.515496 #8841] INFO -- : another-payload: still waiting...
|
46
|
+
I, [2023-12-14T18:48:14.520005 #8841] INFO -- : test payload: still waiting...
|
47
|
+
I, [2023-12-14T18:48:14.520262 #8841] INFO -- : another-payload: stopped gracefully.
|
48
|
+
I, [2023-12-14T18:48:15.524617 #8841] INFO -- : test payload: stopped gracefully.
|
49
|
+
All payloads did stop their execution gracefully.
|
50
|
+
```
|
51
|
+
|
52
|
+
## Work in progress
|
53
|
+
|
54
|
+
* at the moment, only `every` is accepted. Cron formulas could one day be an option but its implementation is more complex.
|
data/lib/seijaku/payload.rb
CHANGED
@@ -4,6 +4,8 @@ module Seijaku
|
|
4
4
|
# Payload refers to the payload YAML file submitted by the user.
|
5
5
|
# it includes tasks, steps, variables and name.
|
6
6
|
class Payload
|
7
|
+
attr_reader :name
|
8
|
+
|
7
9
|
def initialize(payload, logger)
|
8
10
|
@name = payload.fetch("name")
|
9
11
|
@variables = get_variables(payload.fetch("variables"))
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seijaku
|
4
|
+
class Scheduler
|
5
|
+
def initialize(scheduler, logger)
|
6
|
+
@name = scheduler.fetch("name", nil)
|
7
|
+
@logger = logger
|
8
|
+
@scheduler_executors = scheduler.fetch("payloads", []).map do |payload_spec|
|
9
|
+
payload = File.read(payload_spec["payload"]).then do |data|
|
10
|
+
YAML.safe_load(data).then do |data|
|
11
|
+
Payload.new(data, logger)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
SchedulerExecutor.new(payload, payload_spec, logger)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def monitor!
|
20
|
+
thd = []
|
21
|
+
@scheduler_executors.each do |scheduler_executor|
|
22
|
+
@logger.info "scheduling... #{scheduler_executor.name}"
|
23
|
+
thd << Thread.new do
|
24
|
+
scheduler_executor.execute!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
thd.each &:join
|
29
|
+
end
|
30
|
+
|
31
|
+
def soft_exit!
|
32
|
+
@logger.info "Soft exit asked by user, waiting for payloads to stop naturally..."
|
33
|
+
thd = []
|
34
|
+
@scheduler_executors.each do |scheduler_executor|
|
35
|
+
thd << Thread.new do
|
36
|
+
scheduler_executor.status = :exiting
|
37
|
+
while scheduler_executor.status != :exited
|
38
|
+
@logger.info "#{scheduler_executor.name}: still waiting..."
|
39
|
+
sleep 1
|
40
|
+
end
|
41
|
+
|
42
|
+
@logger.info "#{scheduler_executor.name}: stopped gracefully"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
thd.each &:join
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Seijaku
|
2
|
+
class SchedulerExecutor
|
3
|
+
attr_reader :name
|
4
|
+
attr_accessor :status
|
5
|
+
|
6
|
+
def initialize(payload_obj, scheduler_spec, logger)
|
7
|
+
@name = scheduler_spec.fetch("name", nil)
|
8
|
+
@every = scheduler_spec.fetch("every", nil)
|
9
|
+
@payload_file = scheduler_spec.fetch("payload")
|
10
|
+
|
11
|
+
@logger = logger
|
12
|
+
@payload = payload_obj
|
13
|
+
@status = :awaiting
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute!
|
17
|
+
while @status != :exiting do
|
18
|
+
@logger.info "[SCHEDULER] <<- starting execution: #{@name}"
|
19
|
+
|
20
|
+
@status = :started
|
21
|
+
@payload.execute!
|
22
|
+
@status = :awaiting
|
23
|
+
|
24
|
+
@logger.info "[SCHEDULER] <<- finished execution: #{@name} (#{@every}s)"
|
25
|
+
sleep @every
|
26
|
+
end
|
27
|
+
|
28
|
+
@status = :exited
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/seijaku/step.rb
CHANGED
@@ -24,7 +24,7 @@ module Seijaku
|
|
24
24
|
def execute!
|
25
25
|
result = SHExecutor.new(@sh, @variables).run! if @sh
|
26
26
|
result = BashExecutor.new(@bash, @variables).run! if @bash
|
27
|
-
result = SSHExecutor.new(@ssh, @variables, @task, @ssh_hosts).run!
|
27
|
+
result = SSHExecutor.new(@ssh, @variables, @task, @ssh_hosts).run! if @ssh
|
28
28
|
|
29
29
|
if result[:exit_status] != 0
|
30
30
|
logger_output(result)
|
data/lib/seijaku/version.rb
CHANGED
data/lib/seijaku.rb
CHANGED
@@ -12,6 +12,9 @@ require_relative "seijaku/executors/sh"
|
|
12
12
|
require_relative "seijaku/executors/bash"
|
13
13
|
require_relative "seijaku/executors/ssh"
|
14
14
|
|
15
|
+
require_relative "seijaku/scheduler"
|
16
|
+
require_relative "seijaku/scheduler_executor"
|
17
|
+
|
15
18
|
module Seijaku
|
16
19
|
class Error < StandardError; end
|
17
20
|
class VariableError < Error; end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seijaku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kohlrabbit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bcrypt_pbkdf
|
@@ -71,11 +71,14 @@ files:
|
|
71
71
|
- docs/1 - basic.md
|
72
72
|
- docs/2 - variables.md
|
73
73
|
- docs/3 - ssh.md
|
74
|
+
- docs/4 - scheduler.md
|
74
75
|
- lib/seijaku.rb
|
75
76
|
- lib/seijaku/executors/bash.rb
|
76
77
|
- lib/seijaku/executors/sh.rb
|
77
78
|
- lib/seijaku/executors/ssh.rb
|
78
79
|
- lib/seijaku/payload.rb
|
80
|
+
- lib/seijaku/scheduler.rb
|
81
|
+
- lib/seijaku/scheduler_executor.rb
|
79
82
|
- lib/seijaku/ssh/group.rb
|
80
83
|
- lib/seijaku/ssh/host.rb
|
81
84
|
- lib/seijaku/step.rb
|