hybrid_platforms_conductor 33.3.0 → 33.7.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 +41 -0
- data/README.md +31 -2
- data/docs/config_dsl.md +45 -0
- data/docs/plugins/cmdb/host_keys.md +3 -1
- data/docs/plugins/connector/ssh.md +1 -0
- data/lib/hybrid_platforms_conductor/actions_executor.rb +29 -1
- data/lib/hybrid_platforms_conductor/bitbucket.rb +134 -90
- data/lib/hybrid_platforms_conductor/cmd_runner.rb +4 -4
- data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +12 -44
- data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +9 -31
- data/lib/hybrid_platforms_conductor/config.rb +2 -0
- data/lib/hybrid_platforms_conductor/confluence.rb +93 -88
- data/lib/hybrid_platforms_conductor/connector.rb +5 -2
- data/lib/hybrid_platforms_conductor/credentials.rb +122 -97
- data/lib/hybrid_platforms_conductor/deployer.rb +7 -9
- data/lib/hybrid_platforms_conductor/github.rb +39 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/bash.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +27 -17
- data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_keys.rb +13 -12
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/local.rb +6 -4
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +37 -25
- data/lib/hybrid_platforms_conductor/hpc_plugins/log/remote_fs.rb +5 -6
- data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/serverless_chef.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/docker.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +7 -4
- data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +3 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +4 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +17 -3
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +30 -10
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +4 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/idempotence.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +7 -3
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +8 -4
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +3 -1
- data/lib/hybrid_platforms_conductor/logger_helpers.rb +24 -1
- data/lib/hybrid_platforms_conductor/test.rb +21 -7
- data/lib/hybrid_platforms_conductor/tests_runner.rb +7 -6
- data/lib/hybrid_platforms_conductor/thycotic.rb +80 -75
- data/lib/hybrid_platforms_conductor/version.rb +1 -1
- data/spec/hybrid_platforms_conductor_test.rb +6 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/bash_spec.rb +15 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +32 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/local/remote_actions_spec.rb +87 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +30 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +10 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +38 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/helpers_spec.rb +195 -0
- data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +14 -0
- data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +11 -0
- data/spec/hybrid_platforms_conductor_test/api/credentials_spec.rb +251 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/log_plugins/remote_fs_spec.rb +215 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/keepass_spec.rb +280 -319
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +2 -2
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_keys_spec.rb +49 -10
- data/spec/hybrid_platforms_conductor_test/api/platform_handlers/serverless_chef/services_deployment_spec.rb +38 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +49 -69
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +29 -39
- data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +5 -3
- data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/chef_versions.yml +3 -0
- data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/nodes/node.json +15 -0
- data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/policyfiles/test_policy.rb +3 -0
- data/spec/hybrid_platforms_conductor_test/shared_examples/deployer.rb +134 -0
- data/spec/hybrid_platforms_conductor_test/test_connector.rb +2 -2
- metadata +36 -2
|
@@ -59,7 +59,7 @@ module HybridPlatformsConductor
|
|
|
59
59
|
# Raise an exception if the exit status is not the expected one.
|
|
60
60
|
#
|
|
61
61
|
# Parameters::
|
|
62
|
-
# * *cmd* (String): Command to be run
|
|
62
|
+
# * *cmd* (String or SecretString): Command to be run
|
|
63
63
|
# * *log_to_file* (String or nil): Log file capturing stdout or stderr (or nil for none). [default: nil]
|
|
64
64
|
# * *log_to_stdout* (Boolean): Do we send the output to stdout? [default: true]
|
|
65
65
|
# * *log_stdout_to_io* (IO or nil): IO to send command's stdout to, or nil for none. [default: nil]
|
|
@@ -108,7 +108,7 @@ module HybridPlatformsConductor
|
|
|
108
108
|
bash_file = nil
|
|
109
109
|
if force_bash
|
|
110
110
|
bash_file = Tempfile.new('hpc_bash')
|
|
111
|
-
bash_file.write(cmd)
|
|
111
|
+
bash_file.write(cmd.to_unprotected)
|
|
112
112
|
bash_file.chmod 0o700
|
|
113
113
|
bash_file.close
|
|
114
114
|
cmd = "/bin/bash -c #{bash_file.path}"
|
|
@@ -136,7 +136,7 @@ module HybridPlatformsConductor
|
|
|
136
136
|
pty: true,
|
|
137
137
|
timeout: timeout,
|
|
138
138
|
uuid: false
|
|
139
|
-
).run!(cmd) do |stdout, stderr|
|
|
139
|
+
).run!(cmd.to_unprotected) do |stdout, stderr|
|
|
140
140
|
stdout_queue << stdout if stdout
|
|
141
141
|
stderr_queue << stderr if stderr
|
|
142
142
|
end
|
|
@@ -162,7 +162,7 @@ module HybridPlatformsConductor
|
|
|
162
162
|
log_debug "Finished in #{elapsed} seconds with exit status #{exit_status} (#{(expected_code.include?(exit_status) ? 'success'.light_green : 'failure'.light_red).bold})"
|
|
163
163
|
end
|
|
164
164
|
unless expected_code.include?(exit_status)
|
|
165
|
-
error_title = "Command '#{cmd.split("\n").first}' returned error code #{exit_status} (expected #{expected_code.join(', ')})."
|
|
165
|
+
error_title = "Command '#{cmd.to_s.split("\n").first}' returned error code #{exit_status} (expected #{expected_code.join(', ')})."
|
|
166
166
|
if no_exception
|
|
167
167
|
# We consider the caller is responsible for logging what he wants about the details of the error (stdout and stderr)
|
|
168
168
|
log_error error_title
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require 'hybrid_platforms_conductor/bitbucket'
|
|
2
|
-
|
|
3
1
|
module HybridPlatformsConductor
|
|
4
2
|
|
|
5
3
|
module CommonConfigDsl
|
|
@@ -7,12 +5,21 @@ module HybridPlatformsConductor
|
|
|
7
5
|
# Add common Bitbucket config DSL to declare known Bitbucket repositories
|
|
8
6
|
module Bitbucket
|
|
9
7
|
|
|
8
|
+
# List of known Bitbucket repos
|
|
9
|
+
# Array< Hash<Symbol, Object> >
|
|
10
|
+
# * *url* (String): URL to the Bitbucket server
|
|
11
|
+
# * *project* (String): Project name from the Bitbucket server, storing repositories
|
|
12
|
+
# * *repos* (Array<String> or Symbol): List of repository names from this project, or :all for all
|
|
13
|
+
# * *jenkins_ci_url* (String or nil): Corresponding Jenkins CI URL, or nil if none
|
|
14
|
+
# * *checks* (Hash<Symbol, Object>): Checks definition to be perform on those repositories (see the #for_each_bitbucket_repo to know the structure)
|
|
15
|
+
attr_reader :known_bitbucket_repos
|
|
16
|
+
|
|
10
17
|
# Initialize the DSL
|
|
11
18
|
def init_bitbucket
|
|
12
19
|
# List of Bitbucket repositories definitions
|
|
13
20
|
# Array< Hash<Symbol, Object> >
|
|
14
|
-
# Each definition is just mapping the signature of #
|
|
15
|
-
@
|
|
21
|
+
# Each definition is just mapping the signature of #known_bitbucket_repos
|
|
22
|
+
@known_bitbucket_repos = []
|
|
16
23
|
end
|
|
17
24
|
|
|
18
25
|
# Register new Bitbucket repositories
|
|
@@ -24,7 +31,7 @@ module HybridPlatformsConductor
|
|
|
24
31
|
# * *jenkins_ci_url* (String or nil): Corresponding Jenkins CI URL, or nil if none [default: nil]
|
|
25
32
|
# * *checks* (Hash<Symbol, Object>): Checks definition to be perform on those repositories (see the #for_each_bitbucket_repo to know the structure) [default: {}]
|
|
26
33
|
def bitbucket_repos(url:, project:, repos: :all, jenkins_ci_url: nil, checks: {})
|
|
27
|
-
@
|
|
34
|
+
@known_bitbucket_repos << {
|
|
28
35
|
url: url,
|
|
29
36
|
project: project,
|
|
30
37
|
repos: repos,
|
|
@@ -33,45 +40,6 @@ module HybridPlatformsConductor
|
|
|
33
40
|
}
|
|
34
41
|
end
|
|
35
42
|
|
|
36
|
-
# Iterate over each Bitbucket repository
|
|
37
|
-
#
|
|
38
|
-
# Parameters::
|
|
39
|
-
# * Proc: Code called for each Bitbucket repository:
|
|
40
|
-
# * Parameters::
|
|
41
|
-
# * *bitbucket* (Bitbucket): The Bitbucket instance used to query the API for this repository
|
|
42
|
-
# * *repo_info* (Hash<Symbol, Object>): The repository info:
|
|
43
|
-
# * *name* (String): Repository name.
|
|
44
|
-
# * *project* (String): Project name.
|
|
45
|
-
# * *url* (String): Project Git URL.
|
|
46
|
-
# * *jenkins_ci_url* (String or nil): Corresponding Jenkins CI URL, or nil if none.
|
|
47
|
-
# * *checks* (Hash<Symbol, Object>): Checks to be performed on this repository:
|
|
48
|
-
# * *branch_permissions* (Array< Hash<Symbol, Object> >): List of branch permissions to check [optional]
|
|
49
|
-
# * *type* (String): Type of branch permissions to check. Examples of values are 'fast-forward-only', 'no-deletes', 'pull-request-only'.
|
|
50
|
-
# * *branch* (String): Branch on which those permissions apply.
|
|
51
|
-
# * *exempted_users* (Array<String>): List of exempted users for this permission [default: []]
|
|
52
|
-
# * *exempted_groups* (Array<String>): List of exempted groups for this permission [default: []]
|
|
53
|
-
# * *exempted_keys* (Array<String>): List of exempted access keys for this permission [default: []]
|
|
54
|
-
# * *pr_settings* (Hash<Symbol, Object>): PR specific settings to check [optional]
|
|
55
|
-
# * *required_approvers* (Integer): Number of required approvers [optional]
|
|
56
|
-
# * *required_builds* (Integer): Number of required successful builds [optional]
|
|
57
|
-
# * *default_merge_strategy* (String): Name of the default merge strategy. Example: 'rebase-no-ff' [optional]
|
|
58
|
-
# * *mandatory_default_reviewers* (Array<String>): List of mandatory reviewers to check [default: []]
|
|
59
|
-
def for_each_bitbucket_repo
|
|
60
|
-
@bitbucket_repos.each do |bitbucket_repo_info|
|
|
61
|
-
HybridPlatformsConductor::Bitbucket.with_bitbucket(bitbucket_repo_info[:url], @logger, @logger_stderr) do |bitbucket|
|
|
62
|
-
(bitbucket_repo_info[:repos] == :all ? bitbucket.repos(bitbucket_repo_info[:project])['values'].map { |repo_info| repo_info['slug'] } : bitbucket_repo_info[:repos]).each do |name|
|
|
63
|
-
yield bitbucket, {
|
|
64
|
-
name: name,
|
|
65
|
-
project: bitbucket_repo_info[:project],
|
|
66
|
-
url: "#{bitbucket_repo_info[:url]}/scm/#{bitbucket_repo_info[:project].downcase}/#{name}.git",
|
|
67
|
-
jenkins_ci_url: bitbucket_repo_info[:jenkins_ci_url].nil? ? nil : "#{bitbucket_repo_info[:jenkins_ci_url]}/job/#{name}",
|
|
68
|
-
checks: bitbucket_repo_info[:checks]
|
|
69
|
-
}
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
43
|
end
|
|
76
44
|
|
|
77
45
|
end
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
require 'octokit'
|
|
2
|
-
require 'hybrid_platforms_conductor/credentials'
|
|
3
|
-
|
|
4
1
|
module HybridPlatformsConductor
|
|
5
2
|
|
|
6
3
|
module CommonConfigDsl
|
|
@@ -8,12 +5,19 @@ module HybridPlatformsConductor
|
|
|
8
5
|
# Add common Github config DSL to declare known Github repositories
|
|
9
6
|
module Github
|
|
10
7
|
|
|
8
|
+
# List of Github repositories
|
|
9
|
+
# Array< Hash<Symbol, Object> >
|
|
10
|
+
# * *user* (String): User or organization name, storing repositories
|
|
11
|
+
# * *url* (String): URL to the Github API
|
|
12
|
+
# * *repos* (Array<String> or Symbol): List of repository names from this project, or :all for all
|
|
13
|
+
attr_reader :known_github_repos
|
|
14
|
+
|
|
11
15
|
# Initialize the DSL
|
|
12
16
|
def init_github
|
|
13
17
|
# List of Github repositories definitions
|
|
14
18
|
# Array< Hash<Symbol, Object> >
|
|
15
19
|
# Each definition is just mapping the signature of #github_repos
|
|
16
|
-
@
|
|
20
|
+
@known_github_repos = []
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
# Register new Github repositories
|
|
@@ -23,39 +27,13 @@ module HybridPlatformsConductor
|
|
|
23
27
|
# * *url* (String): URL to the Github API [default: 'https://api.github.com']
|
|
24
28
|
# * *repos* (Array<String> or Symbol): List of repository names from this project, or :all for all [default: :all]
|
|
25
29
|
def github_repos(user:, url: 'https://api.github.com', repos: :all)
|
|
26
|
-
@
|
|
30
|
+
@known_github_repos << {
|
|
27
31
|
url: url,
|
|
28
32
|
user: user,
|
|
29
33
|
repos: repos
|
|
30
34
|
}
|
|
31
35
|
end
|
|
32
36
|
|
|
33
|
-
# Iterate over each Github repository
|
|
34
|
-
#
|
|
35
|
-
# Parameters::
|
|
36
|
-
# * Proc: Code called for each Github repository:
|
|
37
|
-
# * Parameters::
|
|
38
|
-
# * *github* (Octokit::Client): The client instance accessing the Github API
|
|
39
|
-
# * *repo_info* (Hash<Symbol, Object>): The repository info:
|
|
40
|
-
# * *name* (String): Repository name.
|
|
41
|
-
# * *slug* (String): Repository slug.
|
|
42
|
-
def for_each_github_repo
|
|
43
|
-
@github_repos.each do |repo_info|
|
|
44
|
-
Octokit.configure do |c|
|
|
45
|
-
c.api_endpoint = repo_info[:url]
|
|
46
|
-
end
|
|
47
|
-
Credentials.with_credentials_for(:github, @logger, @logger_stderr, url: repo_info[:url]) do |_github_user, github_token|
|
|
48
|
-
client = Octokit::Client.new(access_token: github_token)
|
|
49
|
-
(repo_info[:repos] == :all ? client.repositories(repo_info[:user]).map { |repo| repo[:name] } : repo_info[:repos]).each do |name|
|
|
50
|
-
yield client, {
|
|
51
|
-
name: name,
|
|
52
|
-
slug: "#{repo_info[:user]}/#{name}"
|
|
53
|
-
}
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
37
|
end
|
|
60
38
|
|
|
61
39
|
end
|
|
@@ -7,111 +7,116 @@ require 'hybrid_platforms_conductor/credentials'
|
|
|
7
7
|
|
|
8
8
|
module HybridPlatformsConductor
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
|
|
10
|
+
# Mixin used to access Confluence API
|
|
11
|
+
module Confluence
|
|
12
12
|
|
|
13
|
-
include
|
|
13
|
+
include Credentials
|
|
14
14
|
|
|
15
15
|
# Provide a Confluence connector, and make sure the password is being cleaned when exiting.
|
|
16
16
|
#
|
|
17
17
|
# Parameters::
|
|
18
18
|
# * *confluence_url* (String): The Confluence URL
|
|
19
|
-
# * *logger* (Logger): Logger to be used
|
|
20
|
-
# * *logger_stderr* (Logger): Logger to be used for stderr
|
|
21
19
|
# * Proc: Code called with the Confluence instance.
|
|
22
|
-
# * *confluence* (
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
yield
|
|
20
|
+
# * *confluence* (ConfluenceApi): The Confluence instance to use.
|
|
21
|
+
def with_confluence(confluence_url)
|
|
22
|
+
with_credentials_for(:confluence, resource: confluence_url) do |confluence_user, confluence_password|
|
|
23
|
+
yield ConfluenceApi.new(confluence_url, confluence_user, confluence_password, logger: @logger, logger_stderr: @logger_stderr)
|
|
26
24
|
end
|
|
27
25
|
end
|
|
28
26
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
# Parameters::
|
|
32
|
-
# * *confluence_url* (String): The Confluence URL
|
|
33
|
-
# * *confluence_user_name* (String): Confluence user name to be used when querying the API
|
|
34
|
-
# * *confluence_password* (String): Confluence password to be used when querying the API
|
|
35
|
-
# * *logger* (Logger): Logger to be used [default = Logger.new(STDOUT)]
|
|
36
|
-
# * *logger_stderr* (Logger): Logger to be used for stderr [default = Logger.new(STDERR)]
|
|
37
|
-
def initialize(confluence_url, confluence_user_name, confluence_password, logger: Logger.new($stdout), logger_stderr: Logger.new($stderr))
|
|
38
|
-
init_loggers(logger, logger_stderr)
|
|
39
|
-
@confluence_url = confluence_url
|
|
40
|
-
@confluence_user_name = confluence_user_name
|
|
41
|
-
@confluence_password = confluence_password
|
|
42
|
-
end
|
|
27
|
+
# Provide an API access on Confluence
|
|
28
|
+
class ConfluenceApi
|
|
43
29
|
|
|
44
|
-
|
|
45
|
-
#
|
|
46
|
-
# Parameters::
|
|
47
|
-
# * *page_id* (String): Confluence page ID
|
|
48
|
-
# Result::
|
|
49
|
-
# * Nokogiri::HTML: Storage format content, as a Nokogiri object
|
|
50
|
-
def page_storage_format(page_id)
|
|
51
|
-
Nokogiri::HTML(call_api("plugins/viewstorage/viewpagestorage.action?pageId=#{page_id}").body)
|
|
52
|
-
end
|
|
30
|
+
include LoggerHelpers
|
|
53
31
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
32
|
+
# Constructor
|
|
33
|
+
#
|
|
34
|
+
# Parameters::
|
|
35
|
+
# * *confluence_url* (String): The Confluence URL
|
|
36
|
+
# * *confluence_user_name* (String): Confluence user name to be used when querying the API
|
|
37
|
+
# * *confluence_password* (SecretString): Confluence password to be used when querying the API
|
|
38
|
+
# * *logger* (Logger): Logger to be used [default = Logger.new(STDOUT)]
|
|
39
|
+
# * *logger_stderr* (Logger): Logger to be used for stderr [default = Logger.new(STDERR)]
|
|
40
|
+
def initialize(confluence_url, confluence_user_name, confluence_password, logger: Logger.new($stdout), logger_stderr: Logger.new($stderr))
|
|
41
|
+
init_loggers(logger, logger_stderr)
|
|
42
|
+
@confluence_url = confluence_url
|
|
43
|
+
@confluence_user_name = confluence_user_name
|
|
44
|
+
@confluence_password = confluence_password
|
|
45
|
+
end
|
|
63
46
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
version = info['version']['number'] + 1 if version.nil?
|
|
73
|
-
log_debug "Update Confluence page #{page_id}..."
|
|
74
|
-
call_api("rest/api/content/#{page_id}", :put) do |request|
|
|
75
|
-
request['Content-Type'] = 'application/json'
|
|
76
|
-
request.body = {
|
|
77
|
-
type: 'page',
|
|
78
|
-
title: info['title'],
|
|
79
|
-
body: {
|
|
80
|
-
storage: {
|
|
81
|
-
value: content,
|
|
82
|
-
representation: 'storage'
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
version: { number: version }
|
|
86
|
-
}.to_json
|
|
47
|
+
# Return a Confluence storage format content from a page ID
|
|
48
|
+
#
|
|
49
|
+
# Parameters::
|
|
50
|
+
# * *page_id* (String): Confluence page ID
|
|
51
|
+
# Result::
|
|
52
|
+
# * Nokogiri::HTML: Storage format content, as a Nokogiri object
|
|
53
|
+
def page_storage_format(page_id)
|
|
54
|
+
Nokogiri::HTML(call_api("plugins/viewstorage/viewpagestorage.action?pageId=#{page_id}").body)
|
|
87
55
|
end
|
|
88
|
-
end
|
|
89
56
|
|
|
90
|
-
|
|
57
|
+
# Return some info of a given page ID
|
|
58
|
+
#
|
|
59
|
+
# Parameters::
|
|
60
|
+
# * *page_id* (String): Confluence page ID
|
|
61
|
+
# Result::
|
|
62
|
+
# * Hash: Page information, as returned by the Confluence API
|
|
63
|
+
def page_info(page_id)
|
|
64
|
+
JSON.parse(call_api("rest/api/content/#{page_id}").body)
|
|
65
|
+
end
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
67
|
+
# Update a Confluence page to a new content.
|
|
68
|
+
#
|
|
69
|
+
# Parameters::
|
|
70
|
+
# * *page_id* (String): Confluence page ID
|
|
71
|
+
# * *content* (String): New content
|
|
72
|
+
# * *version* (String or nil): New version, or nil to automatically increase last existing version [default: nil]
|
|
73
|
+
def update_page(page_id, content, version: nil)
|
|
74
|
+
info = page_info(page_id)
|
|
75
|
+
version = info['version']['number'] + 1 if version.nil?
|
|
76
|
+
log_debug "Update Confluence page #{page_id}..."
|
|
77
|
+
call_api("rest/api/content/#{page_id}", :put) do |request|
|
|
78
|
+
request['Content-Type'] = 'application/json'
|
|
79
|
+
request.body = {
|
|
80
|
+
type: 'page',
|
|
81
|
+
title: info['title'],
|
|
82
|
+
body: {
|
|
83
|
+
storage: {
|
|
84
|
+
value: content,
|
|
85
|
+
representation: 'storage'
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
version: { number: version }
|
|
89
|
+
}.to_json
|
|
90
|
+
end
|
|
113
91
|
end
|
|
114
|
-
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# Call the Confluence API for a given URL and HTTP verb.
|
|
96
|
+
# Provide a simple way to tweak the request with an optional proc.
|
|
97
|
+
# Automatically handles authentication, base URL and error handling.
|
|
98
|
+
#
|
|
99
|
+
# Parameters::
|
|
100
|
+
# * *api_path* (String): The API path to query
|
|
101
|
+
# * *http_method* (Symbol): HTTP method to be used to create the request [default = :get]
|
|
102
|
+
# * Proc: Optional code called to alter the request
|
|
103
|
+
# * Parameters::
|
|
104
|
+
# * *request* (Net::HTTPRequest): The request
|
|
105
|
+
# Result::
|
|
106
|
+
# * Net::HTTPResponse: The corresponding response
|
|
107
|
+
def call_api(api_path, http_method = :get)
|
|
108
|
+
response = nil
|
|
109
|
+
page_url = URI.parse("#{@confluence_url}/#{api_path}")
|
|
110
|
+
Net::HTTP.start(page_url.host, page_url.port, use_ssl: true) do |http|
|
|
111
|
+
request = Net::HTTP.const_get(http_method.to_s.capitalize.to_sym).new(page_url.request_uri)
|
|
112
|
+
request.basic_auth @confluence_user_name, @confluence_password&.to_unprotected
|
|
113
|
+
yield request if block_given?
|
|
114
|
+
response = http.request(request)
|
|
115
|
+
raise "Confluence page API request on #{page_url} returned an error: #{response.code}\n#{response.body}\n===== Request body =====\n#{request.body}" unless response.is_a?(Net::HTTPSuccess)
|
|
116
|
+
end
|
|
117
|
+
response
|
|
118
|
+
end
|
|
119
|
+
|
|
115
120
|
end
|
|
116
121
|
|
|
117
122
|
end
|
|
@@ -14,16 +14,19 @@ module HybridPlatformsConductor
|
|
|
14
14
|
# * *config* (Config): Config to be used. [default: Config.new]
|
|
15
15
|
# * *cmd_runner* (CmdRunner): Command executor to be used. [default: CmdRunner.new]
|
|
16
16
|
# * *nodes_handler* (NodesHandler): NodesHandler to be used. [default: NodesHandler.new]
|
|
17
|
+
# * *actions_executor* (ActionsExecutor): ActionsExecutor to be used. [default: ActionsExecutor.new]
|
|
17
18
|
def initialize(
|
|
18
19
|
logger: Logger.new($stdout),
|
|
19
20
|
logger_stderr: Logger.new($stderr),
|
|
20
21
|
config: Config.new,
|
|
21
22
|
cmd_runner: CmdRunner.new,
|
|
22
|
-
nodes_handler: NodesHandler.new
|
|
23
|
+
nodes_handler: NodesHandler.new,
|
|
24
|
+
actions_executor: ActionsExecutor.new
|
|
23
25
|
)
|
|
24
26
|
super(logger: logger, logger_stderr: logger_stderr, config: config)
|
|
25
27
|
@cmd_runner = cmd_runner
|
|
26
28
|
@nodes_handler = nodes_handler
|
|
29
|
+
@actions_executor = actions_executor
|
|
27
30
|
# If the connector has an initializer, use it
|
|
28
31
|
init if respond_to?(:init)
|
|
29
32
|
end
|
|
@@ -67,7 +70,7 @@ module HybridPlatformsConductor
|
|
|
67
70
|
# Handle the redirection of standard output and standard error to file and stdout depending on the context of the run.
|
|
68
71
|
#
|
|
69
72
|
# Parameters::
|
|
70
|
-
# * *cmd* (String): The command to be run
|
|
73
|
+
# * *cmd* (String or SecretString): The command to be run
|
|
71
74
|
# * *force_bash* (Boolean): If true, then make sure command is invoked with bash instead of sh [default: false]
|
|
72
75
|
# Result::
|
|
73
76
|
# * Integer: Exit code
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'netrc'
|
|
2
|
+
require 'secret_string'
|
|
2
3
|
require 'uri'
|
|
3
4
|
require 'hybrid_platforms_conductor/logger_helpers'
|
|
4
5
|
|
|
@@ -7,122 +8,146 @@ module HybridPlatformsConductor
|
|
|
7
8
|
# Give a secured and harmonized way to access credentials for a given service.
|
|
8
9
|
# It makes sure to remove passwords from memory for hardened security (this way if a vulnerability allows an attacker to dump the memory it won't get passwords).
|
|
9
10
|
# It gets credentials from the following sources:
|
|
11
|
+
# * Configuration
|
|
10
12
|
# * Environment variables
|
|
11
13
|
# * Netrc file
|
|
12
|
-
|
|
14
|
+
module Credentials
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
# Extend the Config DSL
|
|
17
|
+
module ConfigDSLExtension
|
|
18
|
+
|
|
19
|
+
# List of credentials. Each info has the following properties:
|
|
20
|
+
# * *credential_id* (Symbol): Credential ID this rule applies to
|
|
21
|
+
# * *resource* (Regexp): Resource filtering for this rule
|
|
22
|
+
# * *provider* (Proc): The code providing the credentials:
|
|
23
|
+
# * Parameters::
|
|
24
|
+
# * *resource* (String or nil): The resource for which we want credentials, or nil if none
|
|
25
|
+
# * *requester* (Proc): Code to be called to give credentials to:
|
|
26
|
+
# * Parameters::
|
|
27
|
+
# * *user* (String or nil): The user name, or nil if none
|
|
28
|
+
# * *password* (String or nil): The password, or nil if none
|
|
29
|
+
attr_reader :credentials
|
|
30
|
+
|
|
31
|
+
# Mixin initializer
|
|
32
|
+
def init_credentials_config
|
|
33
|
+
@credentials = []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Define a credentials provider
|
|
37
|
+
#
|
|
38
|
+
# Parameters::
|
|
39
|
+
# * *credential_id* (Symbol): Credential ID this rule applies to
|
|
40
|
+
# * *resource* (String or Regexp): Resource filtering for this rule [default: /.*/]
|
|
41
|
+
# * *provider* (Proc): The code providing the credentials:
|
|
42
|
+
# * Parameters::
|
|
43
|
+
# * *resource* (String or nil): The resource for which we want credentials, or nil if none
|
|
44
|
+
# * *requester* (Proc): Code to be called to give credentials to:
|
|
45
|
+
# * Parameters::
|
|
46
|
+
# * *user* (String or nil): The user name, or nil if none
|
|
47
|
+
# * *password* (String or nil): The password, or nil if none
|
|
48
|
+
def credentials_for(credential_id, resource: /.*/, &provider)
|
|
49
|
+
@credentials << {
|
|
50
|
+
credential_id: credential_id,
|
|
51
|
+
resource: resource.is_a?(String) ? /^#{Regexp.escape(resource)}$/ : resource,
|
|
52
|
+
provider: provider
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Config.extend_config_dsl_with ConfigDSLExtension, :init_credentials_config
|
|
15
59
|
|
|
16
60
|
# Get access to credentials and make sure they are wiped out from memory when client code ends.
|
|
17
61
|
# To ensure password safety, never store the password in a scope beyond the client code's Proc.
|
|
18
62
|
#
|
|
19
63
|
# Parameters::
|
|
20
64
|
# * *id* (Symbol): Credential ID
|
|
21
|
-
# * *
|
|
22
|
-
# * *logger_stderr* (Logger): Logger to be used for stderr
|
|
23
|
-
# * *url* (String or nil): The URL for which we want the credentials, or nil if not associated to a URL [default: nil]
|
|
65
|
+
# * *resource* (String or nil): The resource for which we want the credentials, or nil if not associated to a resource [default: nil]
|
|
24
66
|
# * Proc: Client code called with credentials provided
|
|
25
67
|
# * Parameters::
|
|
26
68
|
# * *user* (String or nil): User name, or nil if none
|
|
27
|
-
# * *password* (
|
|
28
|
-
# !!! Never
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
yield credentials.user, credentials.password
|
|
33
|
-
ensure
|
|
34
|
-
credentials.clear_password
|
|
35
|
-
end
|
|
36
|
-
end
|
|
69
|
+
# * *password* (SecretString or nil): Password, or nil if none.
|
|
70
|
+
# !!! Never clone this password in a scope broader than the client code itself !!!
|
|
71
|
+
def with_credentials_for(id, resource: nil)
|
|
72
|
+
# Get the credentials provider
|
|
73
|
+
provider = nil
|
|
37
74
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def initialize(id, url: nil, logger: Logger.new($stdout), logger_stderr: Logger.new($stderr))
|
|
46
|
-
init_loggers(logger, logger_stderr)
|
|
47
|
-
@id = id
|
|
48
|
-
@url = url
|
|
49
|
-
@user = nil
|
|
50
|
-
@password = nil
|
|
51
|
-
@retrieved = false
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Provide a helper to clear password from memory for security.
|
|
55
|
-
# To be used when the client knows it won't use the password anymore.
|
|
56
|
-
def clear_password
|
|
57
|
-
@password&.replace('gotyou!' * 100)
|
|
58
|
-
GC.start
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Get the associated user
|
|
62
|
-
#
|
|
63
|
-
# Result::
|
|
64
|
-
# * String or nil: The user name, or nil if none
|
|
65
|
-
def user
|
|
66
|
-
retrieve_credentials
|
|
67
|
-
@user
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Get the associated password
|
|
71
|
-
#
|
|
72
|
-
# Result::
|
|
73
|
-
# * String or nil: The password, or nil if none
|
|
74
|
-
def password
|
|
75
|
-
retrieve_credentials
|
|
76
|
-
@password
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
# Retrieve credentials in @user and @password.
|
|
82
|
-
# Do it only once.
|
|
83
|
-
# Make sure the retrieved credentials are not linked to other objects in memory, so that we can remove any other trace of secrets.
|
|
84
|
-
def retrieve_credentials
|
|
85
|
-
return if @retrieved
|
|
75
|
+
# Check configuration
|
|
76
|
+
# Take the last matching provider, this way we can define several providers for resources matched in a increasingly refined way.
|
|
77
|
+
@config.credentials.each do |credentials_info|
|
|
78
|
+
provider = credentials_info[:provider] if credentials_info[:credential_id] == id && (
|
|
79
|
+
(resource.nil? && credentials_info[:resource] == /.*/) || credentials_info[:resource] =~ resource
|
|
80
|
+
)
|
|
81
|
+
end
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if
|
|
93
|
-
log_debug "[ Credentials for #{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
83
|
+
provider ||= proc do |requested_resource, requester|
|
|
84
|
+
# Check environment variables
|
|
85
|
+
user = ENV["hpc_user_for_#{id}"]
|
|
86
|
+
# Clone the password as we are going to treat it as a secret string that will be wiped out
|
|
87
|
+
password = ENV["hpc_password_for_#{id}"].dup
|
|
88
|
+
if user.nil? || user.empty? || password.nil? || password.empty?
|
|
89
|
+
log_debug "[ Credentials for #{id} ] - Credentials not found from environment variables."
|
|
90
|
+
if requested_resource.nil?
|
|
91
|
+
log_debug "[ Credentials for #{id} ] - No resource associated to this credentials, so .netrc can't be used."
|
|
92
|
+
else
|
|
93
|
+
# Check Netrc
|
|
94
|
+
netrc = ::Netrc.read
|
|
95
|
+
begin
|
|
96
|
+
netrc_user, netrc_password = netrc[
|
|
97
|
+
begin
|
|
98
|
+
URI.parse(requested_resource).host.downcase
|
|
99
|
+
rescue URI::InvalidURIError
|
|
100
|
+
requested_resource
|
|
101
|
+
end
|
|
102
|
+
]
|
|
103
|
+
if netrc_user.nil?
|
|
104
|
+
log_debug "[ Credentials for #{id} ] - No credentials retrieved from .netrc."
|
|
105
|
+
# TODO: Add more credentials source if needed here
|
|
106
|
+
log_warn "[ Credentials for #{id} ] - Unable to get credentials for #{id} (Resource: #{requested_resource})."
|
|
107
|
+
else
|
|
108
|
+
# Clone in memory as we are going to wipe out ::Netrc's memory
|
|
109
|
+
user = netrc_user.dup
|
|
110
|
+
password = netrc_password.dup
|
|
111
|
+
log_debug "[ Credentials for #{id} ] - Credentials retrieved from .netrc using #{requested_resource}."
|
|
112
|
+
end
|
|
113
|
+
ensure
|
|
114
|
+
# Make sure the password does not stay in Netrc memory
|
|
115
|
+
# Wipe out any memory trace that might contain passwords in clear
|
|
116
|
+
netrc.instance_variable_get(:@data).each do |data_line|
|
|
117
|
+
data_line.each do |data_string|
|
|
118
|
+
data_string.replace('GotYou!!!' * 100)
|
|
119
|
+
end
|
|
114
120
|
end
|
|
115
121
|
end
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
end
|
|
123
|
+
else
|
|
124
|
+
log_debug "[ Credentials for #{id} ] - Credentials retrieved from environment variables."
|
|
125
|
+
end
|
|
126
|
+
if password.nil?
|
|
127
|
+
requester.call user, password
|
|
128
|
+
else
|
|
129
|
+
SecretString.protect(password) do |secret_password|
|
|
130
|
+
requester.call user, secret_password
|
|
120
131
|
end
|
|
121
132
|
end
|
|
122
|
-
else
|
|
123
|
-
log_debug "[ Credentials for #{@id} ] - Credentials retrieved from environment variables."
|
|
124
133
|
end
|
|
125
|
-
|
|
134
|
+
|
|
135
|
+
requester_called = false
|
|
136
|
+
provider.call(
|
|
137
|
+
resource,
|
|
138
|
+
proc do |user, password|
|
|
139
|
+
requester_called = true
|
|
140
|
+
if password.is_a?(String)
|
|
141
|
+
SecretString.protect(password) do |secret_password|
|
|
142
|
+
yield user, secret_password
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
yield user, password
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
raise "Requester not called by the credentials provider for #{id} (resource: #{resource}) - Please check the credentials_for code in your configuration." unless requester_called
|
|
126
151
|
end
|
|
127
152
|
|
|
128
153
|
end
|