seijaku 0.2.0 → 0.4.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 +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
|
+
[![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,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
|