seijaku 0.1.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 +37 -2
- data/bin/seijaku +33 -8
- data/docs/1 - basic.md +10 -0
- data/docs/2 - variables.md +24 -0
- data/docs/3 - ssh.md +78 -0
- data/docs/4 - scheduler.md +54 -0
- data/lib/seijaku/executors/bash.rb +27 -0
- data/lib/seijaku/executors/sh.rb +27 -0
- data/lib/seijaku/executors/ssh.rb +43 -0
- data/lib/seijaku/payload.rb +35 -0
- data/lib/seijaku/scheduler.rb +48 -0
- data/lib/seijaku/scheduler_executor.rb +31 -0
- data/lib/seijaku/ssh/group.rb +15 -0
- data/lib/seijaku/ssh/host.rb +15 -0
- data/lib/seijaku/step.rb +50 -0
- data/lib/seijaku/task.rb +32 -0
- data/lib/seijaku/variable.rb +24 -0
- data/lib/seijaku/version.rb +1 -1
- data/lib/seijaku.rb +7 -0
- data/seijaku.gemspec +3 -5
- metadata +60 -3
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,22 +1,32 @@
|
|
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
|
|
7
10
|
Payloads are YAML files that describe the various tasks Seijaku will have to perform (in order). Each task contains one or more steps.
|
8
11
|
|
9
|
-
A step is a shell command to be executed. Seijaku currently supports the following shells: bash and
|
12
|
+
A step is a shell command to be executed. Seijaku currently supports the following shells: bash, sh and ssh.
|
10
13
|
|
11
14
|
Each task can have "pre" and "post" tasks, for example to create and delete folders, or install and uninstall software needed to run a task.
|
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
|
18
23
|
name: my payload
|
19
24
|
|
25
|
+
ssh:
|
26
|
+
- host: my-host
|
27
|
+
user: user
|
28
|
+
port: 22
|
29
|
+
|
20
30
|
variables:
|
21
31
|
MY_VARIABLE: a static variable
|
22
32
|
MY_ENV_VARIABLE: $MY_ENV_VARIABLE
|
@@ -37,6 +47,26 @@ tasks:
|
|
37
47
|
soft_fail: true
|
38
48
|
post:
|
39
49
|
- sh: "do something after"
|
50
|
+
|
51
|
+
- name: task with SSH executor
|
52
|
+
host: my-host
|
53
|
+
steps:
|
54
|
+
- ssh: echo "executed on host"
|
55
|
+
```
|
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
|
40
70
|
```
|
41
71
|
|
42
72
|
## Installation
|
@@ -46,11 +76,16 @@ tasks:
|
|
46
76
|
* Ruby 2.5+
|
47
77
|
* Rubygem
|
48
78
|
|
49
|
-
|
50
79
|
Install Seijaku using Gem:
|
51
80
|
|
52
81
|
```bash
|
53
82
|
gem install seijaku
|
54
83
|
```
|
55
84
|
|
85
|
+
## Usage
|
56
86
|
|
87
|
+
```bash
|
88
|
+
seijaku -h
|
89
|
+
seijaku -f ./my-payload.yaml # one-time payload execution
|
90
|
+
seijaku -s ./my-scheduler.yaml # recurrent payloads execution with delay
|
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
|
data/docs/1 - basic.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Variables
|
2
|
+
|
3
|
+
Steps can call environment variables, listed in the `variables` dictionary.
|
4
|
+
|
5
|
+
Variable values can either be given in the payload (static) or be defined from the executive shell.
|
6
|
+
|
7
|
+
```yaml
|
8
|
+
name: payload with variables
|
9
|
+
|
10
|
+
variables:
|
11
|
+
ENV_VARIABLE: value
|
12
|
+
PROVIDED_ENV_VARIABLE: $PROVIDED_ENV_VARIABLE
|
13
|
+
|
14
|
+
tasks:
|
15
|
+
- name: do something useful
|
16
|
+
steps:
|
17
|
+
- sh: echo "sh executor: $ENV_VARIABLE"
|
18
|
+
- bash: echo "bash executor: $PROVIDED_ENV_VARIABLE"
|
19
|
+
```
|
20
|
+
|
21
|
+
```bash
|
22
|
+
export PROVIDED_ENV_VARIABLE=value
|
23
|
+
seijaku -f ./docs/variables.yaml
|
24
|
+
```
|
data/docs/3 - ssh.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# SSH
|
2
|
+
|
3
|
+
Since version `0.1.0`, Seijaku implements a SSH step executor. Only one SSH host can be set per task.
|
4
|
+
|
5
|
+
|
6
|
+
```yaml
|
7
|
+
name: payload with SSH executor
|
8
|
+
|
9
|
+
ssh:
|
10
|
+
- host: my-host
|
11
|
+
port: 22
|
12
|
+
user: ubuntu
|
13
|
+
|
14
|
+
tasks:
|
15
|
+
- name: do something useful
|
16
|
+
host: my-host
|
17
|
+
steps:
|
18
|
+
- ssh: echo "connection test"
|
19
|
+
```
|
20
|
+
|
21
|
+
It is recommended to run `ssh-add` before running Seijaku, as each step would require you to enter your password.
|
22
|
+
|
23
|
+
## Variables
|
24
|
+
|
25
|
+
Variables can be set from the Seijaku executive host and ran in the SSH host context:
|
26
|
+
|
27
|
+
```yaml
|
28
|
+
name: payload with SSH executor (variables)
|
29
|
+
|
30
|
+
ssh:
|
31
|
+
- host: my-host
|
32
|
+
port: 22
|
33
|
+
user: ubuntu
|
34
|
+
|
35
|
+
variables:
|
36
|
+
ENV_VARIABLE: value
|
37
|
+
PROVIDED_ENV_VARIABLE: $PROVIDED_ENV_VARIABLE
|
38
|
+
|
39
|
+
tasks:
|
40
|
+
- name: do something useful
|
41
|
+
host: my-host
|
42
|
+
steps:
|
43
|
+
- ssh: echo "connection test"
|
44
|
+
- ssh: echo "$ENV_VARIABLE"
|
45
|
+
- ssh: echo "$PROVIDED_ENV_VARIABLE"
|
46
|
+
```
|
47
|
+
|
48
|
+
## Bastion / Jump host / Proxy
|
49
|
+
|
50
|
+
You sometimes need to jump over a publicly exposed server to reach another one. Seijaku supports SSH Proxy, with a maximum of 1 hop.
|
51
|
+
|
52
|
+
```yaml
|
53
|
+
name: payload with SSH executor (bastion)
|
54
|
+
|
55
|
+
ssh:
|
56
|
+
- host: bastion-host
|
57
|
+
port: 22
|
58
|
+
user: bastion_user
|
59
|
+
|
60
|
+
- host: my-host
|
61
|
+
port: 22
|
62
|
+
user: ubuntu
|
63
|
+
bastion: bastion-host
|
64
|
+
|
65
|
+
variables:
|
66
|
+
ENV_VARIABLE: value
|
67
|
+
PROVIDED_ENV_VARIABLE: $PROVIDED_ENV_VARIABLE
|
68
|
+
|
69
|
+
tasks:
|
70
|
+
- name: do something useful
|
71
|
+
host: my-host
|
72
|
+
steps:
|
73
|
+
- ssh: echo "connection test"
|
74
|
+
- ssh: echo "$ENV_VARIABLE"
|
75
|
+
- ssh: echo "$PROVIDED_ENV_VARIABLE"
|
76
|
+
```
|
77
|
+
|
78
|
+
The `ssh.[].host` must be a reachable address (domain name or IP).
|
@@ -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.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
|
5
|
+
module Seijaku
|
6
|
+
# executes `bash` commands
|
7
|
+
class BashExecutor
|
8
|
+
def initialize(raw, variables)
|
9
|
+
@command = ["bash", "-c", "'#{raw}'"]
|
10
|
+
@variables = variables
|
11
|
+
end
|
12
|
+
|
13
|
+
def run!
|
14
|
+
stdout, stderr, exit_status = Open3.capture3(
|
15
|
+
@variables,
|
16
|
+
@command.join(" ")
|
17
|
+
)
|
18
|
+
|
19
|
+
{
|
20
|
+
stdout: stdout.chomp,
|
21
|
+
stderr: stderr.chomp,
|
22
|
+
exit_status: exit_status.exitstatus,
|
23
|
+
command: @command.join(" ")
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
|
5
|
+
module Seijaku
|
6
|
+
# Execute `sh` commands
|
7
|
+
class SHExecutor
|
8
|
+
def initialize(raw, variables)
|
9
|
+
@command = ["sh", "-c", "'#{raw}'"]
|
10
|
+
@variables = variables
|
11
|
+
end
|
12
|
+
|
13
|
+
def run!
|
14
|
+
stdout, stderr, exit_status = Open3.capture3(
|
15
|
+
@variables,
|
16
|
+
@command.join(" ")
|
17
|
+
)
|
18
|
+
|
19
|
+
{
|
20
|
+
stdout: stdout.chomp,
|
21
|
+
stderr: stderr.chomp,
|
22
|
+
exit_status: exit_status.exitstatus,
|
23
|
+
command: @command.join(" ")
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/ssh"
|
4
|
+
require "net/ssh/proxy/jump"
|
5
|
+
|
6
|
+
module Seijaku
|
7
|
+
# SSHExecutor connects to SSH host and runs command
|
8
|
+
class SSHExecutor
|
9
|
+
def initialize(raw, variables, task, ssh_hosts)
|
10
|
+
@command = ["sh", "-c", "'#{raw}'"]
|
11
|
+
@hosts = ssh_hosts
|
12
|
+
@variables = variables
|
13
|
+
@task = task
|
14
|
+
@ssh_hosts = ssh_hosts
|
15
|
+
|
16
|
+
raise SSHExecutorError, "no ssh host defined in payload", [] if ssh_hosts.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
def run!
|
20
|
+
machine = @ssh_hosts.hosts[@task.host]
|
21
|
+
result = { command: @command.join(" "), stdout: nil, stderr: nil }
|
22
|
+
status = {}
|
23
|
+
options = machine.bastion ? { proxy: bastion_setup } : {}
|
24
|
+
ssh = Net::SSH.start(machine.host, machine.user, port: machine.port, **options)
|
25
|
+
ssh.exec!(@command.join(" "), status: status) do |_ch, stream, data|
|
26
|
+
result[:stdout] = data.chomp if stream == :stdout
|
27
|
+
result[:stderr] = data.chomp unless stream == :stdout
|
28
|
+
end
|
29
|
+
ssh.close
|
30
|
+
|
31
|
+
result[:exit_status] = status[:exit_code]
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def bastion_setup
|
36
|
+
bastion_name = @ssh_hosts.hosts[@task.host].bastion
|
37
|
+
bastion_host = @ssh_hosts.hosts[bastion_name]
|
38
|
+
|
39
|
+
connect_str = "#{bastion_host.user}@#{bastion_host.host}:#{bastion_host.port}"
|
40
|
+
Net::SSH::Proxy::Jump.new(connect_str)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seijaku
|
4
|
+
# Payload refers to the payload YAML file submitted by the user.
|
5
|
+
# it includes tasks, steps, variables and name.
|
6
|
+
class Payload
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def initialize(payload, logger)
|
10
|
+
@name = payload.fetch("name")
|
11
|
+
@variables = get_variables(payload.fetch("variables"))
|
12
|
+
@ssh_hosts = SSHGroup.new(payload.fetch("ssh", []))
|
13
|
+
@tasks = payload.fetch("tasks").map do |task|
|
14
|
+
Task.new(task, @variables, logger, @ssh_hosts)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute!
|
19
|
+
@tasks.each(&:execute!)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_variables(payload_variables)
|
23
|
+
sorted_variables = {}
|
24
|
+
variables = payload_variables.map do |var|
|
25
|
+
Variable.new(var)
|
26
|
+
end
|
27
|
+
|
28
|
+
variables.each do |var|
|
29
|
+
sorted_variables.merge!({ var.key => var.value })
|
30
|
+
end
|
31
|
+
|
32
|
+
sorted_variables
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -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
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seijaku
|
4
|
+
# Host of SSHGroup
|
5
|
+
class Host
|
6
|
+
attr_reader :host, :port, :user, :bastion
|
7
|
+
|
8
|
+
def initialize(host)
|
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
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/seijaku/step.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seijaku
|
4
|
+
# A step is a bash or sh command that must be executed.
|
5
|
+
# steps execution is always fifo.
|
6
|
+
class Step
|
7
|
+
attr_reader :command, :pipeline
|
8
|
+
|
9
|
+
def initialize(step, variables, pipeline, logger, task, ssh_hosts = nil)
|
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
|
+
@variables = variables
|
16
|
+
@pipeline = pipeline
|
17
|
+
@logger = logger
|
18
|
+
@task = task
|
19
|
+
@ssh_hosts = ssh_hosts
|
20
|
+
|
21
|
+
@command = (@sh || @bash) || @ssh
|
22
|
+
end
|
23
|
+
|
24
|
+
def execute!
|
25
|
+
result = SHExecutor.new(@sh, @variables).run! if @sh
|
26
|
+
result = BashExecutor.new(@bash, @variables).run! if @bash
|
27
|
+
result = SSHExecutor.new(@ssh, @variables, @task, @ssh_hosts).run! if @ssh
|
28
|
+
|
29
|
+
if result[:exit_status] != 0
|
30
|
+
logger_output(result)
|
31
|
+
exit(1) unless @soft_fail
|
32
|
+
end
|
33
|
+
|
34
|
+
return unless @output
|
35
|
+
|
36
|
+
%i[stdout stderr].each do |stream|
|
37
|
+
puts format("%<stream>s:\t %<result>s", stream: stream, result: result[stream])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def logger_output(result)
|
42
|
+
@logger.info <<~OUTPUT
|
43
|
+
command: `#{result[:command]}`
|
44
|
+
exit_code: #{result[:exit_status]}
|
45
|
+
stdout: #{result[:stdout]}
|
46
|
+
stderr: #{result[:stderr]}"
|
47
|
+
OUTPUT
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/seijaku/task.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seijaku
|
4
|
+
# Task is composed of a name, an array of Step (Pre, Post)
|
5
|
+
class Task
|
6
|
+
attr_reader :host
|
7
|
+
|
8
|
+
def initialize(task, variables, logger, ssh_hosts = nil)
|
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) }
|
12
|
+
@pre_steps = task.fetch("pre", []).map { |step| Step.new(step, variables, :pre, logger, self, ssh_hosts) }
|
13
|
+
@post_steps = task.fetch("post", []).map { |step| Step.new(step, variables, :post, logger, self, ssh_hosts) }
|
14
|
+
@logger = logger
|
15
|
+
|
16
|
+
raise TaskError, "no name set in task", [] if @name.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute!
|
20
|
+
[@pre_steps, @steps, @post_steps].each do |pipeline_steps|
|
21
|
+
pipeline_steps.each do |step|
|
22
|
+
@logger.info(format("[%<name>s:%<pipeline>s] running %<command>s",
|
23
|
+
name: @name,
|
24
|
+
pipeline: step.pipeline,
|
25
|
+
command: step.command))
|
26
|
+
|
27
|
+
step.execute!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seijaku
|
4
|
+
# variable
|
5
|
+
class Variable
|
6
|
+
attr_reader :key, :value
|
7
|
+
|
8
|
+
def initialize(variable)
|
9
|
+
@key = variable.first
|
10
|
+
|
11
|
+
value = variable.last
|
12
|
+
@value = value
|
13
|
+
|
14
|
+
return unless value.start_with?("$")
|
15
|
+
|
16
|
+
env_key = value.split("$").last
|
17
|
+
env_value = ENV.fetch(env_key, nil)
|
18
|
+
|
19
|
+
raise VariableError, "no value set for #{env_key}", [] if env_value.nil?
|
20
|
+
|
21
|
+
@value = env_value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/seijaku/version.rb
CHANGED
data/lib/seijaku.rb
CHANGED
@@ -5,12 +5,19 @@ require_relative "seijaku/payload"
|
|
5
5
|
require_relative "seijaku/variable"
|
6
6
|
require_relative "seijaku/task"
|
7
7
|
require_relative "seijaku/step"
|
8
|
+
require_relative "seijaku/ssh/group"
|
9
|
+
require_relative "seijaku/ssh/host"
|
8
10
|
|
9
11
|
require_relative "seijaku/executors/sh"
|
10
12
|
require_relative "seijaku/executors/bash"
|
13
|
+
require_relative "seijaku/executors/ssh"
|
14
|
+
|
15
|
+
require_relative "seijaku/scheduler"
|
16
|
+
require_relative "seijaku/scheduler_executor"
|
11
17
|
|
12
18
|
module Seijaku
|
13
19
|
class Error < StandardError; end
|
14
20
|
class VariableError < Error; end
|
15
21
|
class TaskError < Error; end
|
22
|
+
class SSHExecutorError < Error; end
|
16
23
|
end
|
data/seijaku.gemspec
CHANGED
@@ -32,9 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.executables = %w[seijaku]
|
33
33
|
spec.require_paths = ["lib"]
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# For more information and examples about making a new gem, check out our
|
39
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
35
|
+
spec.add_dependency "bcrypt_pbkdf", "~> 1.1"
|
36
|
+
spec.add_dependency "ed25519", "~> 1.3"
|
37
|
+
spec.add_dependency "net-ssh", "~> 7.2"
|
40
38
|
end
|
metadata
CHANGED
@@ -1,15 +1,57 @@
|
|
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-
|
12
|
-
dependencies:
|
11
|
+
date: 2023-12-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bcrypt_pbkdf
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ed25519
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: net-ssh
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '7.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '7.2'
|
13
55
|
description: CLI tool ingesting YAML files to execute tasks on different shells with
|
14
56
|
pre and post routines.
|
15
57
|
email:
|
@@ -26,7 +68,22 @@ files:
|
|
26
68
|
- README.md
|
27
69
|
- Rakefile
|
28
70
|
- bin/seijaku
|
71
|
+
- docs/1 - basic.md
|
72
|
+
- docs/2 - variables.md
|
73
|
+
- docs/3 - ssh.md
|
74
|
+
- docs/4 - scheduler.md
|
29
75
|
- lib/seijaku.rb
|
76
|
+
- lib/seijaku/executors/bash.rb
|
77
|
+
- lib/seijaku/executors/sh.rb
|
78
|
+
- lib/seijaku/executors/ssh.rb
|
79
|
+
- lib/seijaku/payload.rb
|
80
|
+
- lib/seijaku/scheduler.rb
|
81
|
+
- lib/seijaku/scheduler_executor.rb
|
82
|
+
- lib/seijaku/ssh/group.rb
|
83
|
+
- lib/seijaku/ssh/host.rb
|
84
|
+
- lib/seijaku/step.rb
|
85
|
+
- lib/seijaku/task.rb
|
86
|
+
- lib/seijaku/variable.rb
|
30
87
|
- lib/seijaku/version.rb
|
31
88
|
- seijaku.gemspec
|
32
89
|
- sig/seijaku.rbs
|