hybrid_platforms_conductor 32.16.3 → 33.0.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 +42 -0
- data/README.md +6 -3
- data/bin/last_deploys +4 -1
- data/bin/nodes_to_deploy +5 -5
- data/docs/config_dsl.md +45 -1
- data/docs/executables.md +6 -7
- data/docs/executables/check-node.md +3 -3
- data/docs/executables/deploy.md +3 -3
- data/docs/executables/dump_nodes_json.md +3 -3
- data/docs/executables/test.md +3 -3
- data/docs/executables/topograph.md +3 -3
- data/docs/gen/mermaid/README.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/check-node.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/deploy.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/free_ips.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/get_impacted_nodes.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/last_deploys.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/nodes_to_deploy.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/report.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/run.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/ssh_config.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/test.md-0.png +0 -0
- data/docs/plugins.md +47 -0
- data/docs/plugins/connector/ssh.md +1 -1
- data/docs/plugins/log/remote_fs.md +26 -0
- data/docs/plugins/secrets_reader/cli.md +31 -0
- data/docs/plugins/secrets_reader/thycotic.md +46 -0
- data/docs/plugins/test/bitbucket_conf.md +1 -1
- data/docs/plugins/test/check_deploy_and_idempotence.md +1 -1
- data/docs/plugins/test/connection.md +1 -0
- data/docs/plugins/test/deploy_removes_root_access.md +1 -1
- data/docs/plugins/test/file_system.md +1 -0
- data/docs/plugins/test/github_ci.md +48 -0
- data/docs/plugins/test/hostname.md +1 -0
- data/docs/plugins/test/ip.md +1 -0
- data/docs/plugins/test/jenkins_ci_conf.md +1 -1
- data/docs/plugins/test/jenkins_ci_masters_ok.md +1 -1
- data/docs/plugins/test/local_users.md +1 -0
- data/docs/plugins/test/mounts.md +1 -0
- data/docs/plugins/test/orphan_files.md +1 -0
- data/docs/plugins/test/ports.md +1 -0
- data/docs/plugins/test/spectre.md +1 -0
- data/docs/plugins/test/vulnerabilities.md +1 -0
- data/lib/hybrid_platforms_conductor/actions_executor.rb +8 -1
- data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +62 -0
- data/lib/hybrid_platforms_conductor/deployer.rb +193 -141
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +3 -3
- data/lib/hybrid_platforms_conductor/hpc_plugins/log/my_log_plugin.rb.sample +100 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/log/remote_fs.rb +179 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/cli.rb +75 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/my_secrets_reader_plugin.rb.sample +46 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +87 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/connection.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_freshness.rb +7 -20
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +2 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +32 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/ports.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +2 -1
- data/lib/hybrid_platforms_conductor/log.rb +31 -0
- data/lib/hybrid_platforms_conductor/plugins.rb +1 -0
- data/lib/hybrid_platforms_conductor/secrets_reader.rb +31 -0
- data/lib/hybrid_platforms_conductor/test_only_remote_node.rb +18 -0
- data/lib/hybrid_platforms_conductor/version.rb +1 -1
- data/spec/hybrid_platforms_conductor_test.rb +27 -6
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +3 -3
- data/spec/hybrid_platforms_conductor_test/api/deployer/config_dsl_spec.rb +46 -4
- data/spec/hybrid_platforms_conductor_test/api/deployer/deploy_spec.rb +187 -212
- data/spec/hybrid_platforms_conductor_test/api/deployer/log_plugins/remote_fs_spec.rb +223 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioner_spec.rb +4 -4
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/cli_spec.rb +63 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +253 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/global_spec.rb +1 -1
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +72 -0
- data/spec/hybrid_platforms_conductor_test/executables/last_deploys_spec.rb +146 -98
- data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +240 -83
- data/spec/hybrid_platforms_conductor_test/executables/options/common_spec.rb +2 -1
- data/spec/hybrid_platforms_conductor_test/executables/options/deployer_spec.rb +0 -182
- data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +1 -1
- data/spec/hybrid_platforms_conductor_test/helpers/deployer_helpers.rb +40 -53
- data/spec/hybrid_platforms_conductor_test/helpers/deployer_test_helpers.rb +251 -15
- data/spec/hybrid_platforms_conductor_test/test_log_no_read_plugin.rb +82 -0
- data/spec/hybrid_platforms_conductor_test/test_log_plugin.rb +103 -0
- data/spec/hybrid_platforms_conductor_test/test_secrets_reader_plugin.rb +45 -0
- metadata +41 -2
|
@@ -238,7 +238,7 @@ module HybridPlatformsConductor
|
|
|
238
238
|
# * *bash_cmds* (String): Bash commands to execute
|
|
239
239
|
def remote_bash(bash_cmds)
|
|
240
240
|
ssh_cmd =
|
|
241
|
-
if @nodes_handler.get_ssh_session_exec_of(@node) ==
|
|
241
|
+
if @nodes_handler.get_ssh_session_exec_of(@node) == false
|
|
242
242
|
# When ExecSession is disabled we need to use stdin directly
|
|
243
243
|
"{ cat | #{ssh_exec} #{ssh_url} -T; } <<'HPC_EOF'\n#{bash_cmds}\nHPC_EOF"
|
|
244
244
|
else
|
|
@@ -300,7 +300,7 @@ module HybridPlatformsConductor
|
|
|
300
300
|
# * *owner* (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
|
|
301
301
|
# * *group* (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
|
|
302
302
|
def remote_copy(from, to, sudo: false, owner: nil, group: nil)
|
|
303
|
-
if @nodes_handler.get_ssh_session_exec_of(@node) ==
|
|
303
|
+
if @nodes_handler.get_ssh_session_exec_of(@node) == false
|
|
304
304
|
# We don't have ExecSession, so don't use ssh, but scp instead.
|
|
305
305
|
if sudo
|
|
306
306
|
# We need to first copy the file in an accessible directory, and then sudo mv
|
|
@@ -513,7 +513,7 @@ module HybridPlatformsConductor
|
|
|
513
513
|
if current_users.empty?
|
|
514
514
|
log_debug "[ ControlMaster - #{ssh_url} ] - Creating SSH ControlMaster..."
|
|
515
515
|
exit_status = nil
|
|
516
|
-
if @nodes_handler.get_ssh_session_exec_of(node) ==
|
|
516
|
+
if @nodes_handler.get_ssh_session_exec_of(node) == false
|
|
517
517
|
# Here we have to create a ControlMaster using an interactive session, as the SSH server prohibits ExecSession, and so command executions.
|
|
518
518
|
# We'll do that using another terminal spawned in the background.
|
|
519
519
|
if ENV['hpc_interactive'] == 'false'
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require 'hybrid_platforms_conductor/log'
|
|
2
|
+
|
|
3
|
+
module HybridPlatformsConductor
|
|
4
|
+
|
|
5
|
+
module HpcPlugins
|
|
6
|
+
|
|
7
|
+
module Log
|
|
8
|
+
|
|
9
|
+
# Save logs on the remote node's file system
|
|
10
|
+
class RemoteFs < HybridPlatformsConductor::Log
|
|
11
|
+
|
|
12
|
+
# Get actions to save logs
|
|
13
|
+
# [API] - This method is mandatory.
|
|
14
|
+
# [API] - The following API components are accessible:
|
|
15
|
+
# * *@config* (Config): Main configuration API.
|
|
16
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
|
17
|
+
# * *@actions_executor* (ActionsExecutor): Actions executor API.
|
|
18
|
+
#
|
|
19
|
+
# Parameters::
|
|
20
|
+
# * *node* (String): Node for which logs are being saved
|
|
21
|
+
# * *services* (Array<String>): The list of services that have been deployed on this node
|
|
22
|
+
# * *deployment_info* (Hash<Symbol,Object>): Additional information to attach to the logs
|
|
23
|
+
# * *exit_status* (Integer or Symbol): Exit status of the deployment
|
|
24
|
+
# * *stdout* (String): Deployment's stdout
|
|
25
|
+
# * *stderr* (String): Deployment's stderr
|
|
26
|
+
# Result::
|
|
27
|
+
# * Array< Hash<Symbol,Object> >: List of actions to be done
|
|
28
|
+
def actions_to_save_logs(node, services, deployment_info, exit_status, stdout, stderr)
|
|
29
|
+
# Return here all actions that are to be run to save those logs.
|
|
30
|
+
[
|
|
31
|
+
{
|
|
32
|
+
bash: <<~EOS
|
|
33
|
+
cat <<'EOLOGFILE' >>all_logs.txt
|
|
34
|
+
Deployment log on #{node}:
|
|
35
|
+
#{stdout}
|
|
36
|
+
EOLOGFILE
|
|
37
|
+
EOS
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get actions to read logs.
|
|
43
|
+
# If provided, this method can return some actions to be executed that will fetch logs from servers or remote nodes.
|
|
44
|
+
# By using this method to run actions instead of the synchronous method logs_from, such actions will be run in parallel which can greatly improve time-consuming operations when querying a lot of nodes.
|
|
45
|
+
# [API] - This method is optional.
|
|
46
|
+
# [API] - The following API components are accessible:
|
|
47
|
+
# * *@config* (Config): Main configuration API.
|
|
48
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
|
49
|
+
# * *@actions_executor* (ActionsExecutor): Actions executor API.
|
|
50
|
+
#
|
|
51
|
+
# Parameters::
|
|
52
|
+
# * *node* (String): Node for which deployment logs are being read
|
|
53
|
+
# Result::
|
|
54
|
+
# * Array< Hash<Symbol,Object> >: List of actions to be done
|
|
55
|
+
def actions_to_read_logs(node)
|
|
56
|
+
[
|
|
57
|
+
{ bash: 'cat all_logs.txt' }
|
|
58
|
+
]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Get deployment logs from a node.
|
|
62
|
+
# This method can use the result of actions previously run to read logs, as returned by the actions_to_read_logs method.
|
|
63
|
+
# [API] - This method is mandatory.
|
|
64
|
+
# [API] - The following API components are accessible:
|
|
65
|
+
# * *@config* (Config): Main configuration API.
|
|
66
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
|
67
|
+
# * *@actions_executor* (ActionsExecutor): Actions executor API.
|
|
68
|
+
#
|
|
69
|
+
# Parameters::
|
|
70
|
+
# * *node* (String): The node we want deployment logs from
|
|
71
|
+
# * *exit_status* (Integer, Symbol or nil): Exit status of actions to read logs, or nil if no action was returned by actions_to_read_logs
|
|
72
|
+
# * *stdout* (String or nil): stdout of actions to read logs, or nil if no action was returned by actions_to_read_logs
|
|
73
|
+
# * *stderr* (String or nil): stderr of actions to read logs, or nil if no action was returned by actions_to_read_logs
|
|
74
|
+
# Result::
|
|
75
|
+
# * Hash<Symbol,Object>: Deployment log information:
|
|
76
|
+
# * *error* (String): Error string in case deployment logs could not be retrieved. If set then further properties will be ignored. [optional]
|
|
77
|
+
# * *services* (Array<String>): List of services deployed on the node
|
|
78
|
+
# * *deployment_info* (Hash<Symbol,Object>): Deployment metadata
|
|
79
|
+
# * *exit_status* (Integer or Symbol): Deployment exit status
|
|
80
|
+
# * *stdout* (String): Deployment stdout
|
|
81
|
+
# * *stderr* (String): Deployment stderr
|
|
82
|
+
def logs_for(node, exit_status, stdout, stderr)
|
|
83
|
+
# Here we should retrieve this info from somewhere.
|
|
84
|
+
# Based on this example, stdout is the value of the execution of the action 'cat all_logs.txt' as returned by actions_to_read_logs.
|
|
85
|
+
{
|
|
86
|
+
services: %w[unknown],
|
|
87
|
+
deployment_info: {},
|
|
88
|
+
exit_status: 0,
|
|
89
|
+
stdout: stdout,
|
|
90
|
+
stderr: ''
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
require 'hybrid_platforms_conductor/log'
|
|
2
|
+
|
|
3
|
+
module HybridPlatformsConductor
|
|
4
|
+
|
|
5
|
+
module HpcPlugins
|
|
6
|
+
|
|
7
|
+
module Log
|
|
8
|
+
|
|
9
|
+
# Save logs on the remote node's file system
|
|
10
|
+
class RemoteFs < HybridPlatformsConductor::Log
|
|
11
|
+
|
|
12
|
+
MARKER_STDOUT = '===== STDOUT ====='
|
|
13
|
+
MARKER_STDERR = '===== STDERR ====='
|
|
14
|
+
|
|
15
|
+
# Get actions to save logs
|
|
16
|
+
# [API] - This method is mandatory.
|
|
17
|
+
# [API] - The following API components are accessible:
|
|
18
|
+
# * *@config* (Config): Main configuration API.
|
|
19
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
|
20
|
+
# * *@actions_executor* (ActionsExecutor): Actions executor API.
|
|
21
|
+
#
|
|
22
|
+
# Parameters::
|
|
23
|
+
# * *node* (String): Node for which logs are being saved
|
|
24
|
+
# * *services* (Array<String>): The list of services that have been deployed on this node
|
|
25
|
+
# * *deployment_info* (Hash<Symbol,Object>): Additional information to attach to the logs
|
|
26
|
+
# * *exit_status* (Integer or Symbol): Exit status of the deployment
|
|
27
|
+
# * *stdout* (String): Deployment's stdout
|
|
28
|
+
# * *stderr* (String): Deployment's stderr
|
|
29
|
+
# Result::
|
|
30
|
+
# * Array< Hash<Symbol,Object> >: List of actions to be done
|
|
31
|
+
def actions_to_save_logs(node, services, deployment_info, exit_status, stdout, stderr)
|
|
32
|
+
# Create a log file to be scp with all relevant info
|
|
33
|
+
ssh_user = @actions_executor.connector(:ssh).ssh_user
|
|
34
|
+
sudo_prefix = ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} "
|
|
35
|
+
log_file = "#{Dir.tmpdir}/hpc_deploy_logs/#{node}_#{Time.now.utc.strftime('%F_%H%M%S')}_#{ssh_user}"
|
|
36
|
+
[
|
|
37
|
+
{
|
|
38
|
+
ruby: proc do
|
|
39
|
+
FileUtils.mkdir_p File.dirname(log_file)
|
|
40
|
+
File.write(log_file, <<~EOS)
|
|
41
|
+
#{
|
|
42
|
+
deployment_info.merge(
|
|
43
|
+
debug: log_debug? ? 'Yes' : 'No',
|
|
44
|
+
services: services.join(', '),
|
|
45
|
+
exit_status: exit_status
|
|
46
|
+
).map { |property, value| "#{property}: #{value}" }.join("\n")
|
|
47
|
+
}
|
|
48
|
+
#{MARKER_STDOUT}
|
|
49
|
+
#{stdout}
|
|
50
|
+
#{MARKER_STDERR}
|
|
51
|
+
#{stderr}
|
|
52
|
+
EOS
|
|
53
|
+
end,
|
|
54
|
+
remote_bash: "#{sudo_prefix}mkdir -p /var/log/deployments && #{sudo_prefix}chmod 600 /var/log/deployments"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
scp: {
|
|
58
|
+
log_file => '/var/log/deployments',
|
|
59
|
+
:sudo => ssh_user != 'root',
|
|
60
|
+
:owner => 'root',
|
|
61
|
+
:group => 'root'
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
remote_bash: "#{sudo_prefix}chmod 600 /var/log/deployments/#{File.basename(log_file)}",
|
|
66
|
+
# Remove temporary files storing logs for security
|
|
67
|
+
ruby: proc do
|
|
68
|
+
File.unlink(log_file)
|
|
69
|
+
end
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get actions to read logs.
|
|
75
|
+
# If provided, this method can return some actions to be executed that will fetch logs from servers or remote nodes.
|
|
76
|
+
# By using this method to run actions instead of the synchronous method logs_from, such actions will be run in parallel which can greatly improve time-consuming operations when querying a lot of nodes.
|
|
77
|
+
# [API] - This method is optional.
|
|
78
|
+
# [API] - The following API components are accessible:
|
|
79
|
+
# * *@config* (Config): Main configuration API.
|
|
80
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
|
81
|
+
# * *@actions_executor* (ActionsExecutor): Actions executor API.
|
|
82
|
+
#
|
|
83
|
+
# Parameters::
|
|
84
|
+
# * *node* (String): Node for which deployment logs are being read
|
|
85
|
+
# Result::
|
|
86
|
+
# * Array< Hash<Symbol,Object> >: List of actions to be done
|
|
87
|
+
def actions_to_read_logs(node)
|
|
88
|
+
sudo_prefix = @actions_executor.connector(:ssh).ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} "
|
|
89
|
+
[
|
|
90
|
+
{ remote_bash: "#{sudo_prefix}cat /var/log/deployments/`#{sudo_prefix}ls -t /var/log/deployments/ | head -1`" }
|
|
91
|
+
]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Get deployment logs from a node.
|
|
95
|
+
# This method can use the result of actions previously run to read logs, as returned by the actions_to_read_logs method.
|
|
96
|
+
# [API] - This method is mandatory.
|
|
97
|
+
# [API] - The following API components are accessible:
|
|
98
|
+
# * *@config* (Config): Main configuration API.
|
|
99
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
|
100
|
+
# * *@actions_executor* (ActionsExecutor): Actions executor API.
|
|
101
|
+
#
|
|
102
|
+
# Parameters::
|
|
103
|
+
# * *node* (String): The node we want deployment logs from
|
|
104
|
+
# * *exit_status* (Integer, Symbol or nil): Exit status of actions to read logs, or nil if no action was returned by actions_to_read_logs
|
|
105
|
+
# * *stdout* (String or nil): stdout of actions to read logs, or nil if no action was returned by actions_to_read_logs
|
|
106
|
+
# * *stderr* (String or nil): stderr of actions to read logs, or nil if no action was returned by actions_to_read_logs
|
|
107
|
+
# Result::
|
|
108
|
+
# * Hash<Symbol,Object>: Deployment log information:
|
|
109
|
+
# * *error* (String): Error string in case deployment logs could not be retrieved. If set then further properties will be ignored. [optional]
|
|
110
|
+
# * *services* (Array<String>): List of services deployed on the node
|
|
111
|
+
# * *deployment_info* (Hash<Symbol,Object>): Deployment metadata
|
|
112
|
+
# * *exit_status* (Integer or Symbol): Deployment exit status
|
|
113
|
+
# * *stdout* (String): Deployment stdout
|
|
114
|
+
# * *stderr* (String): Deployment stderr
|
|
115
|
+
def logs_for(node, exit_status, stdout, stderr)
|
|
116
|
+
# Expected format for stdout:
|
|
117
|
+
# Property1: Value1
|
|
118
|
+
# ...
|
|
119
|
+
# PropertyN: ValueN
|
|
120
|
+
# ===== STDOUT =====
|
|
121
|
+
# ...
|
|
122
|
+
# ===== STDERR =====
|
|
123
|
+
# ...
|
|
124
|
+
if exit_status.is_a?(Symbol)
|
|
125
|
+
{ error: "Error: #{exit_status}\n#{stderr}" }
|
|
126
|
+
else
|
|
127
|
+
stdout_lines = stdout.split("\n")
|
|
128
|
+
if stdout_lines.first =~ /No such file or directory/
|
|
129
|
+
{ error: '/var/log/deployments missing' }
|
|
130
|
+
else
|
|
131
|
+
stdout_idx = stdout_lines.index(MARKER_STDOUT)
|
|
132
|
+
stderr_idx = stdout_lines.index(MARKER_STDERR)
|
|
133
|
+
deploy_info = {}
|
|
134
|
+
stdout_lines[0..stdout_idx - 1].each do |line|
|
|
135
|
+
if line =~ /^([^:]+): (.+)$/
|
|
136
|
+
key_str, value = $1, $2
|
|
137
|
+
key = key_str.to_sym
|
|
138
|
+
# Type-cast some values
|
|
139
|
+
case key_str
|
|
140
|
+
when 'date'
|
|
141
|
+
# Date and time values
|
|
142
|
+
# Thu Nov 23 18:43:01 UTC 2017
|
|
143
|
+
deploy_info[key] = Time.parse("#{value} UTC")
|
|
144
|
+
when 'debug'
|
|
145
|
+
# Boolean values
|
|
146
|
+
# Yes
|
|
147
|
+
deploy_info[key] = (value == 'Yes')
|
|
148
|
+
when /^diff_files_.+$/, 'services'
|
|
149
|
+
# Array of strings
|
|
150
|
+
# my_file.txt, other_file.txt
|
|
151
|
+
deploy_info[key] = value.split(', ')
|
|
152
|
+
else
|
|
153
|
+
deploy_info[key] = value
|
|
154
|
+
end
|
|
155
|
+
else
|
|
156
|
+
deploy_info[:unknown_lines] = [] unless deploy_info.key?(:unknown_lines)
|
|
157
|
+
deploy_info[:unknown_lines] << line
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
services = deploy_info.delete(:services)
|
|
161
|
+
exit_status = deploy_info.delete(:exit_status)
|
|
162
|
+
{
|
|
163
|
+
services: services,
|
|
164
|
+
deployment_info: deploy_info,
|
|
165
|
+
exit_status: exit_status =~ /^\d+$/ ? Integer(exit_status) : exit_status.to_sym,
|
|
166
|
+
stdout: stdout_lines[stdout_idx + 1..stderr_idx - 1].join("\n"),
|
|
167
|
+
stderr: stdout_lines[stderr_idx + 1..-1].join("\n")
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'hybrid_platforms_conductor/secrets_reader'
|
|
2
|
+
|
|
3
|
+
module HybridPlatformsConductor
|
|
4
|
+
|
|
5
|
+
module HpcPlugins
|
|
6
|
+
|
|
7
|
+
module SecretsReader
|
|
8
|
+
|
|
9
|
+
# Get secrets from the command-line
|
|
10
|
+
class Cli < HybridPlatformsConductor::SecretsReader
|
|
11
|
+
|
|
12
|
+
# Constructor
|
|
13
|
+
#
|
|
14
|
+
# Parameters::
|
|
15
|
+
# * *logger* (Logger): Logger to be used [default: Logger.new(STDOUT)]
|
|
16
|
+
# * *logger_stderr* (Logger): Logger to be used for stderr [default: Logger.new(STDERR)]
|
|
17
|
+
# * *config* (Config): Config to be used. [default: Config.new]
|
|
18
|
+
# * *cmd_runner* (CmdRunner): CmdRunner to be used. [default: CmdRunner.new]
|
|
19
|
+
# * *nodes_handler* (NodesHandler): Nodes handler to be used. [default: NodesHandler.new]
|
|
20
|
+
def initialize(
|
|
21
|
+
logger: Logger.new(STDOUT),
|
|
22
|
+
logger_stderr: Logger.new(STDERR),
|
|
23
|
+
config: Config.new,
|
|
24
|
+
cmd_runner: CmdRunner.new,
|
|
25
|
+
nodes_handler: NodesHandler.new
|
|
26
|
+
)
|
|
27
|
+
super
|
|
28
|
+
@secrets_files = []
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Complete an option parser with options meant to control this secrets reader
|
|
32
|
+
# [API] - This method is optional
|
|
33
|
+
#
|
|
34
|
+
# Parameters::
|
|
35
|
+
# * *options_parser* (OptionParser): The option parser to complete
|
|
36
|
+
def options_parse(options_parser)
|
|
37
|
+
options_parser.on('-e', '--secrets JSON_FILE', 'Specify a secrets location from a local JSON file. Can be specified several times.') do |file|
|
|
38
|
+
@secrets_files << file
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Return secrets for a given service to be deployed on a node.
|
|
43
|
+
# [API] - This method is mandatory
|
|
44
|
+
# [API] - The following API components are accessible:
|
|
45
|
+
# * *@config* (Config): Main configuration API.
|
|
46
|
+
# * *@cmd_runner* (CmdRunner): Command Runner API.
|
|
47
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
|
48
|
+
#
|
|
49
|
+
# Parameters::
|
|
50
|
+
# * *node* (String): Node to be deployed
|
|
51
|
+
# * *service* (String): Service to be deployed
|
|
52
|
+
# Result::
|
|
53
|
+
# * Hash: The secrets
|
|
54
|
+
def secrets_for(node, service)
|
|
55
|
+
# As we are dealing with global secrets, cache the reading for performance between nodes and services.
|
|
56
|
+
unless defined?(@secrets)
|
|
57
|
+
@secrets = {}
|
|
58
|
+
@secrets_files.each do |secrets_file|
|
|
59
|
+
raise "Missing secrets file: #{secrets_file}" unless File.exist?(secrets_file)
|
|
60
|
+
@secrets.merge!(JSON.parse(File.read(secrets_file))) do |key, value1, value2|
|
|
61
|
+
raise "Secret #{key} has conflicting values between different secret JSON files." if value1 != value2
|
|
62
|
+
value1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
@secrets
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/my_secrets_reader_plugin.rb.sample
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'hybrid_platforms_conductor/secrets_reader'
|
|
2
|
+
|
|
3
|
+
module HybridPlatformsConductor
|
|
4
|
+
|
|
5
|
+
module HpcPlugins
|
|
6
|
+
|
|
7
|
+
module SecretsReader
|
|
8
|
+
|
|
9
|
+
# Read secrets from a secrets source
|
|
10
|
+
class MySecretsReaderPlugin < HybridPlatformsConductor::SecretsReader
|
|
11
|
+
|
|
12
|
+
# Complete an option parser with options meant to control this secrets reader
|
|
13
|
+
# [API] - This method is optional
|
|
14
|
+
#
|
|
15
|
+
# Parameters::
|
|
16
|
+
# * *options_parser* (OptionParser): The option parser to complete
|
|
17
|
+
def options_parse(options_parser)
|
|
18
|
+
@key_file = nil
|
|
19
|
+
options_parser.on('--key-file FILE', 'Key file decrypting a secret vault.') do |file|
|
|
20
|
+
@key_file = file
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Return secrets for a given service to be deployed on a node.
|
|
25
|
+
# [API] - This method is mandatory
|
|
26
|
+
# [API] - The following API components are accessible:
|
|
27
|
+
# * *@config* (Config): Main configuration API.
|
|
28
|
+
# * *@cmd_runner* (CmdRunner): Command Runner API.
|
|
29
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
|
30
|
+
#
|
|
31
|
+
# Parameters::
|
|
32
|
+
# * *node* (String): Node to be deployed
|
|
33
|
+
# * *service* (String): Service to be deployed
|
|
34
|
+
# Result::
|
|
35
|
+
# * Hash: The secrets
|
|
36
|
+
def secrets_for(node, service)
|
|
37
|
+
JSON.parse(Vault.decrypt("/path/to/#{node}_#{service}.vault", key: @key_file))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'hybrid_platforms_conductor/secrets_reader'
|
|
2
|
+
require 'hybrid_platforms_conductor/thycotic'
|
|
3
|
+
|
|
4
|
+
module HybridPlatformsConductor
|
|
5
|
+
|
|
6
|
+
module HpcPlugins
|
|
7
|
+
|
|
8
|
+
module SecretsReader
|
|
9
|
+
|
|
10
|
+
# Get secrets from a Thycotic secrets server
|
|
11
|
+
class Thycotic < HybridPlatformsConductor::SecretsReader
|
|
12
|
+
|
|
13
|
+
# Extend the Config DSL
|
|
14
|
+
module ConfigDSLExtension
|
|
15
|
+
|
|
16
|
+
# List of defined Thycotic secrets. Each info has the following properties:
|
|
17
|
+
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule.
|
|
18
|
+
# * *thycotic_url* (String): Thycotic URL.
|
|
19
|
+
# * *secret_id* (Integer): Thycotic secret ID.
|
|
20
|
+
# Array< Hash<Symbol, Object> >
|
|
21
|
+
attr_reader :thycotic_secrets
|
|
22
|
+
|
|
23
|
+
# Mixin initializer
|
|
24
|
+
def init_thycotic_config
|
|
25
|
+
@thycotic_secrets = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Set a Thycotic secret server configuration
|
|
29
|
+
#
|
|
30
|
+
# Parameters::
|
|
31
|
+
# * *thycotic_url* (String): The Thycotic server URL.
|
|
32
|
+
# * *secret_id* (Integer): The Thycotic secret ID containing the secrets file to be used as secrets.
|
|
33
|
+
def secrets_from_thycotic(thycotic_url:, secret_id:)
|
|
34
|
+
@thycotic_secrets << {
|
|
35
|
+
nodes_selectors_stack: current_nodes_selectors_stack,
|
|
36
|
+
thycotic_url: thycotic_url,
|
|
37
|
+
secret_id: secret_id
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Config.extend_config_dsl_with ConfigDSLExtension, :init_thycotic_config
|
|
44
|
+
|
|
45
|
+
# Return secrets for a given service to be deployed on a node.
|
|
46
|
+
# [API] - This method is mandatory
|
|
47
|
+
# [API] - The following API components are accessible:
|
|
48
|
+
# * *@config* (Config): Main configuration API.
|
|
49
|
+
# * *@cmd_runner* (CmdRunner): Command Runner API.
|
|
50
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
|
51
|
+
#
|
|
52
|
+
# Parameters::
|
|
53
|
+
# * *node* (String): Node to be deployed
|
|
54
|
+
# * *service* (String): Service to be deployed
|
|
55
|
+
# Result::
|
|
56
|
+
# * Hash: The secrets
|
|
57
|
+
def secrets_for(node, service)
|
|
58
|
+
secrets = {}
|
|
59
|
+
# As we are dealing with global secrets, cache the reading for performance between nodes and services.
|
|
60
|
+
# Keep secrets cache grouped by URL/ID
|
|
61
|
+
@secrets = {} unless defined?(@secrets)
|
|
62
|
+
@nodes_handler.select_confs_for_node(node, @config.thycotic_secrets).each do |thycotic_secrets_info|
|
|
63
|
+
server_id = "#{thycotic_secrets_info[:thycotic_url]}:#{thycotic_secrets_info[:secret_id]}"
|
|
64
|
+
unless @secrets.key?(server_id)
|
|
65
|
+
HybridPlatformsConductor::Thycotic.with_thycotic(thycotic_secrets_info[:thycotic_url], @logger, @logger_stderr) do |thycotic|
|
|
66
|
+
secret_file_item_id = thycotic.get_secret(thycotic_secrets_info[:secret_id]).dig(:secret, :items, :secret_item, :id)
|
|
67
|
+
raise "Unable to fetch secret file ID #{thycotic_secrets_info[:secret_id]} from #{thycotic_secrets_info[:thycotic_url]}" if secret_file_item_id.nil?
|
|
68
|
+
secret = thycotic.download_file_attachment_by_item_id(thycotic_secrets_info[:secret_id], secret_file_item_id)
|
|
69
|
+
raise "Unable to fetch secret file attachment from secret ID #{thycotic_secrets_info[:secret_id]} from #{thycotic_secrets_info[:thycotic_url]}" if secret.nil?
|
|
70
|
+
@secrets[server_id] = JSON.parse(secret)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
secrets.merge!(@secrets[server_id]) do |key, value1, value2|
|
|
74
|
+
raise "Thycotic secret #{key} served by #{thycotic_secrets_info[:thycotic_url]} from secret ID #{thycotic_secrets_info[:secret_id]} has conflicting values between different secrets." if value1 != value2
|
|
75
|
+
value1
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
secrets
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|