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.
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