hybrid_platforms_conductor 32.16.3 → 33.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/README.md +6 -3
  4. data/bin/last_deploys +4 -1
  5. data/bin/nodes_to_deploy +5 -5
  6. data/docs/config_dsl.md +45 -1
  7. data/docs/executables.md +6 -7
  8. data/docs/executables/check-node.md +3 -3
  9. data/docs/executables/deploy.md +3 -3
  10. data/docs/executables/dump_nodes_json.md +3 -3
  11. data/docs/executables/test.md +3 -3
  12. data/docs/executables/topograph.md +3 -3
  13. data/docs/gen/mermaid/README.md-0.png +0 -0
  14. data/docs/gen/mermaid/docs/executables/check-node.md-0.png +0 -0
  15. data/docs/gen/mermaid/docs/executables/deploy.md-0.png +0 -0
  16. data/docs/gen/mermaid/docs/executables/free_ips.md-0.png +0 -0
  17. data/docs/gen/mermaid/docs/executables/get_impacted_nodes.md-0.png +0 -0
  18. data/docs/gen/mermaid/docs/executables/last_deploys.md-0.png +0 -0
  19. data/docs/gen/mermaid/docs/executables/nodes_to_deploy.md-0.png +0 -0
  20. data/docs/gen/mermaid/docs/executables/report.md-0.png +0 -0
  21. data/docs/gen/mermaid/docs/executables/run.md-0.png +0 -0
  22. data/docs/gen/mermaid/docs/executables/ssh_config.md-0.png +0 -0
  23. data/docs/gen/mermaid/docs/executables/test.md-0.png +0 -0
  24. data/docs/plugins.md +47 -0
  25. data/docs/plugins/connector/ssh.md +1 -1
  26. data/docs/plugins/log/remote_fs.md +26 -0
  27. data/docs/plugins/secrets_reader/cli.md +31 -0
  28. data/docs/plugins/secrets_reader/thycotic.md +46 -0
  29. data/docs/plugins/test/bitbucket_conf.md +1 -1
  30. data/docs/plugins/test/check_deploy_and_idempotence.md +1 -1
  31. data/docs/plugins/test/connection.md +1 -0
  32. data/docs/plugins/test/deploy_removes_root_access.md +1 -1
  33. data/docs/plugins/test/file_system.md +1 -0
  34. data/docs/plugins/test/github_ci.md +48 -0
  35. data/docs/plugins/test/hostname.md +1 -0
  36. data/docs/plugins/test/ip.md +1 -0
  37. data/docs/plugins/test/jenkins_ci_conf.md +1 -1
  38. data/docs/plugins/test/jenkins_ci_masters_ok.md +1 -1
  39. data/docs/plugins/test/local_users.md +1 -0
  40. data/docs/plugins/test/mounts.md +1 -0
  41. data/docs/plugins/test/orphan_files.md +1 -0
  42. data/docs/plugins/test/ports.md +1 -0
  43. data/docs/plugins/test/spectre.md +1 -0
  44. data/docs/plugins/test/vulnerabilities.md +1 -0
  45. data/lib/hybrid_platforms_conductor/actions_executor.rb +8 -1
  46. data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +62 -0
  47. data/lib/hybrid_platforms_conductor/deployer.rb +193 -141
  48. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +3 -3
  49. data/lib/hybrid_platforms_conductor/hpc_plugins/log/my_log_plugin.rb.sample +100 -0
  50. data/lib/hybrid_platforms_conductor/hpc_plugins/log/remote_fs.rb +179 -0
  51. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/cli.rb +75 -0
  52. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/my_secrets_reader_plugin.rb.sample +46 -0
  53. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +87 -0
  54. data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +1 -1
  55. data/lib/hybrid_platforms_conductor/hpc_plugins/test/connection.rb +3 -1
  56. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_freshness.rb +7 -20
  57. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +1 -1
  58. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +2 -1
  59. data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +32 -0
  60. data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +3 -1
  61. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +3 -1
  62. data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +3 -1
  63. data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +3 -1
  64. data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +3 -1
  65. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ports.rb +3 -1
  66. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +3 -1
  67. data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +2 -1
  68. data/lib/hybrid_platforms_conductor/log.rb +31 -0
  69. data/lib/hybrid_platforms_conductor/plugins.rb +1 -0
  70. data/lib/hybrid_platforms_conductor/secrets_reader.rb +31 -0
  71. data/lib/hybrid_platforms_conductor/test_only_remote_node.rb +18 -0
  72. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  73. data/spec/hybrid_platforms_conductor_test.rb +27 -6
  74. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +3 -3
  75. data/spec/hybrid_platforms_conductor_test/api/deployer/config_dsl_spec.rb +46 -4
  76. data/spec/hybrid_platforms_conductor_test/api/deployer/deploy_spec.rb +187 -212
  77. data/spec/hybrid_platforms_conductor_test/api/deployer/log_plugins/remote_fs_spec.rb +223 -0
  78. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioner_spec.rb +4 -4
  79. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/cli_spec.rb +63 -0
  80. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +253 -0
  81. data/spec/hybrid_platforms_conductor_test/api/tests_runner/global_spec.rb +1 -1
  82. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +72 -0
  83. data/spec/hybrid_platforms_conductor_test/executables/last_deploys_spec.rb +146 -98
  84. data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +240 -83
  85. data/spec/hybrid_platforms_conductor_test/executables/options/common_spec.rb +2 -1
  86. data/spec/hybrid_platforms_conductor_test/executables/options/deployer_spec.rb +0 -182
  87. data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +1 -1
  88. data/spec/hybrid_platforms_conductor_test/helpers/deployer_helpers.rb +40 -53
  89. data/spec/hybrid_platforms_conductor_test/helpers/deployer_test_helpers.rb +251 -15
  90. data/spec/hybrid_platforms_conductor_test/test_log_no_read_plugin.rb +82 -0
  91. data/spec/hybrid_platforms_conductor_test/test_log_plugin.rb +103 -0
  92. data/spec/hybrid_platforms_conductor_test/test_secrets_reader_plugin.rb +45 -0
  93. 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) == 'false'
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) == 'false'
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) == 'false'
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
@@ -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