seijaku 0.2.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +13 -1
- data/README.md +22 -1
- data/bin/seijaku +34 -8
- data/docs/4 - scheduler.md +54 -0
- data/lib/seijaku/executors/ssh.rb +15 -5
- data/lib/seijaku/payload.rb +8 -5
- data/lib/seijaku/scheduler.rb +48 -0
- data/lib/seijaku/scheduler_executor.rb +31 -0
- data/lib/seijaku/ssh/group.rb +1 -1
- data/lib/seijaku/ssh/host.rb +4 -4
- data/lib/seijaku/step.rb +8 -7
- data/lib/seijaku/task.rb +6 -6
- data/lib/seijaku/version.rb +1 -1
- data/lib/seijaku.rb +3 -0
- data/sig/seijaku.rbs +11 -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: 0b351498f055e85f7e20edc44374616118d0162b479104d18f20b271138a9271
|
4
|
+
data.tar.gz: '08c117438ac1e83a15fef55c6973e27fd15ef1d2fbc03a7027774deee4904720'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23d86d8bad1a17e32a8b52faebe48873369c7eabf8988fbeec15e854d2e4b834aa8d29dab583dbfb121f18de545289bb1f4ab7484cd7bea787c0aba04cfcd33d
|
7
|
+
data.tar.gz: '0532610288a45a4e8391f71da9caa73d2ac22f2a02af7a99af4629fdaa9b3efe60a2fdcf7696337030822e2a24d8d1f832855b447f88292e9d92e4774f843134'
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
##
|
1
|
+
## Seijaku
|
2
|
+
|
3
|
+
Please mind Seijaku is still under heavy development.
|
4
|
+
|
5
|
+
## [0.3.0] - 2023-12-14
|
6
|
+
|
7
|
+
- scheduler has been added to run reccurent payloads
|
8
|
+
- fixed a bug related to SSHExecutor set as default executor
|
9
|
+
|
10
|
+
## [0.2.0] - 2023-12-12
|
11
|
+
|
12
|
+
- SSH executor added with `ssh` YAML dictionnary and `host` Task support.
|
13
|
+
- Documentation added in `./docs` directory
|
2
14
|
|
3
15
|
## [0.1.0] - 2023-12-11
|
4
16
|
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Seijaku
|
2
2
|
|
3
|
+
[](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,42 @@ 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
|
+
symbolize_names: true
|
46
|
+
)
|
39
47
|
|
40
|
-
|
41
|
-
|
48
|
+
logger.info "Starting Seijaku. Payload: #{payload_file["name"]}"
|
49
|
+
|
50
|
+
payload = Payload.new(payload_file, logger)
|
51
|
+
payload.execute!
|
52
|
+
end
|
53
|
+
|
54
|
+
if options[:scheduler]
|
55
|
+
scheduler_file = YAML.safe_load(
|
56
|
+
File.read(options[:scheduler])
|
57
|
+
)
|
58
|
+
|
59
|
+
logger.info "Starting Seijaku. Scheduler: #{scheduler_file["name"]}"
|
60
|
+
begin
|
61
|
+
scheduler = Scheduler.new(scheduler_file, logger)
|
62
|
+
scheduler.monitor!
|
63
|
+
rescue Interrupt
|
64
|
+
scheduler.soft_exit!
|
65
|
+
puts "All payloads did stop their execution gracefully."
|
66
|
+
end
|
67
|
+
end
|
42
68
|
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.
|
@@ -6,12 +6,19 @@ require "net/ssh/proxy/jump"
|
|
6
6
|
module Seijaku
|
7
7
|
# SSHExecutor connects to SSH host and runs command
|
8
8
|
class SSHExecutor
|
9
|
-
def initialize(raw, variables, task, ssh_hosts)
|
10
|
-
@command = ["sh", "-c", "'#{raw}'"]
|
9
|
+
def initialize(raw, variables, task, ssh_hosts, ssh_settings)
|
11
10
|
@hosts = ssh_hosts
|
12
|
-
@variables = variables
|
11
|
+
@variables = variables.map do |key, value|
|
12
|
+
"#{key}='#{value}'"
|
13
|
+
end.join(" ")
|
14
|
+
|
15
|
+
@command = ["#{@variables};", "#{raw}"]
|
13
16
|
@task = task
|
14
17
|
@ssh_hosts = ssh_hosts
|
18
|
+
@ssh_settings = ssh_settings.transform_keys(&:to_sym)
|
19
|
+
if @ssh_settings[:verify_host_key].eql?("never")
|
20
|
+
@ssh_settings[:verify_host_key] = :never
|
21
|
+
end
|
15
22
|
|
16
23
|
raise SSHExecutorError, "no ssh host defined in payload", [] if ssh_hosts.nil?
|
17
24
|
end
|
@@ -20,12 +27,15 @@ module Seijaku
|
|
20
27
|
machine = @ssh_hosts.hosts[@task.host]
|
21
28
|
result = { command: @command.join(" "), stdout: nil, stderr: nil }
|
22
29
|
status = {}
|
23
|
-
options = machine.bastion ? { proxy: bastion_setup } :
|
24
|
-
|
30
|
+
options = machine.bastion ? { proxy: bastion_setup, **@ssh_settings } : @ssh_settings
|
31
|
+
|
32
|
+
ssh = Net::SSH.start(machine.host, machine.user, {port: machine.port, **@ssh_settings})
|
33
|
+
|
25
34
|
ssh.exec!(@command.join(" "), status: status) do |_ch, stream, data|
|
26
35
|
result[:stdout] = data.chomp if stream == :stdout
|
27
36
|
result[:stderr] = data.chomp unless stream == :stdout
|
28
37
|
end
|
38
|
+
|
29
39
|
ssh.close
|
30
40
|
|
31
41
|
result[:exit_status] = status[:exit_code]
|
data/lib/seijaku/payload.rb
CHANGED
@@ -4,12 +4,15 @@ 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
|
-
@name = payload.fetch(
|
9
|
-
@variables = get_variables(payload.fetch(
|
10
|
-
@ssh_hosts = SSHGroup.new(payload.fetch(
|
11
|
-
@
|
12
|
-
|
10
|
+
@name = payload.fetch(:name)
|
11
|
+
@variables = get_variables(payload.fetch(:variables))
|
12
|
+
@ssh_hosts = SSHGroup.new(payload.fetch(:ssh, []))
|
13
|
+
@ssh_settings = payload.fetch(:ssh_settings, {})
|
14
|
+
@tasks = payload.fetch(:tasks).map do |task|
|
15
|
+
Task.new(task, @variables, logger, @ssh_hosts, @ssh_settings)
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
@@ -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/ssh/group.rb
CHANGED
data/lib/seijaku/ssh/host.rb
CHANGED
@@ -6,10 +6,10 @@ module Seijaku
|
|
6
6
|
attr_reader :host, :port, :user, :bastion
|
7
7
|
|
8
8
|
def initialize(host)
|
9
|
-
@host = host.fetch(
|
10
|
-
@port = host.fetch(
|
11
|
-
@bastion = host.fetch(
|
12
|
-
@user = host.fetch(
|
9
|
+
@host = host.fetch(:host, "localhost")
|
10
|
+
@port = host.fetch(:port, 22)
|
11
|
+
@bastion = host.fetch(:bastion, nil)
|
12
|
+
@user = host.fetch(:user, "root")
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
data/lib/seijaku/step.rb
CHANGED
@@ -6,17 +6,18 @@ module Seijaku
|
|
6
6
|
class Step
|
7
7
|
attr_reader :command, :pipeline
|
8
8
|
|
9
|
-
def initialize(step, variables, pipeline, logger, task, ssh_hosts = nil)
|
10
|
-
@sh = step.fetch(
|
11
|
-
@bash = step.fetch(
|
12
|
-
@ssh = step.fetch(
|
13
|
-
@soft_fail = step.fetch(
|
14
|
-
@output = step.fetch(
|
9
|
+
def initialize(step, variables, pipeline, logger, task, ssh_hosts = nil, ssh_settings = {})
|
10
|
+
@sh = step.fetch(:sh, nil)
|
11
|
+
@bash = step.fetch(:bash, nil)
|
12
|
+
@ssh = step.fetch(:ssh, nil)
|
13
|
+
@soft_fail = step.fetch(:soft_fail, false)
|
14
|
+
@output = step.fetch(:output, false)
|
15
15
|
@variables = variables
|
16
16
|
@pipeline = pipeline
|
17
17
|
@logger = logger
|
18
18
|
@task = task
|
19
19
|
@ssh_hosts = ssh_hosts
|
20
|
+
@ssh_settings = ssh_settings
|
20
21
|
|
21
22
|
@command = (@sh || @bash) || @ssh
|
22
23
|
end
|
@@ -24,7 +25,7 @@ module Seijaku
|
|
24
25
|
def execute!
|
25
26
|
result = SHExecutor.new(@sh, @variables).run! if @sh
|
26
27
|
result = BashExecutor.new(@bash, @variables).run! if @bash
|
27
|
-
result = SSHExecutor.new(@ssh, @variables, @task, @ssh_hosts).run!
|
28
|
+
result = SSHExecutor.new(@ssh, @variables, @task, @ssh_hosts, @ssh_settings).run!
|
28
29
|
|
29
30
|
if result[:exit_status] != 0
|
30
31
|
logger_output(result)
|
data/lib/seijaku/task.rb
CHANGED
@@ -5,12 +5,12 @@ module Seijaku
|
|
5
5
|
class Task
|
6
6
|
attr_reader :host
|
7
7
|
|
8
|
-
def initialize(task, variables, logger, ssh_hosts = nil)
|
9
|
-
@name = task.fetch(
|
10
|
-
@host = task.fetch(
|
11
|
-
@steps = task.fetch(
|
12
|
-
@pre_steps = task.fetch(
|
13
|
-
@post_steps = task.fetch(
|
8
|
+
def initialize(task, variables, logger, ssh_hosts = nil, ssh_settings = {})
|
9
|
+
@name = task.fetch(:name, nil)
|
10
|
+
@host = task.fetch(:host, nil)
|
11
|
+
@steps = task.fetch(:steps, []).map { |step| Step.new(step, variables, :steps, logger, self, ssh_hosts, ssh_settings) }
|
12
|
+
@pre_steps = task.fetch(:pre, []).map { |step| Step.new(step, variables, :pre, logger, self, ssh_hosts, ssh_settings) }
|
13
|
+
@post_steps = task.fetch(:post, []).map { |step| Step.new(step, variables, :post, logger, self, ssh_hosts, ssh_settings) }
|
14
14
|
@logger = logger
|
15
15
|
|
16
16
|
raise TaskError, "no name set in task", [] if @name.nil?
|
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
|
data/sig/seijaku.rbs
CHANGED
@@ -1,4 +1,15 @@
|
|
1
1
|
module Seijaku
|
2
2
|
VERSION: String
|
3
3
|
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
|
4
|
+
class Error < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class TaskError < Error
|
8
|
+
end
|
9
|
+
|
10
|
+
class VariableError < Error
|
11
|
+
end
|
12
|
+
|
13
|
+
class SSHExecutorError < Error
|
14
|
+
end
|
4
15
|
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.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kohlrabbit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-27 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
|