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.
- 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
@@ -0,0 +1,31 @@
|
|
1
|
+
# Secrets reader plugin: `cli`
|
2
|
+
|
3
|
+
The `cli` secrets reader plugin reads secrets from a local JSON file that can be given through the `--secrets` command-line parameter.
|
4
|
+
|
5
|
+
Example:
|
6
|
+
```bash
|
7
|
+
./bin/deploy --node my_node --secrets /path/to/my_secrets.json
|
8
|
+
```
|
9
|
+
|
10
|
+
## Config DSL extension
|
11
|
+
|
12
|
+
None
|
13
|
+
|
14
|
+
## Used credentials
|
15
|
+
|
16
|
+
| Credential | Usage
|
17
|
+
| --- | --- |
|
18
|
+
|
19
|
+
## Used Metadata
|
20
|
+
|
21
|
+
| Metadata | Type | Usage
|
22
|
+
| --- | --- | --- |
|
23
|
+
|
24
|
+
## Used environment variables
|
25
|
+
|
26
|
+
| Variable | Usage
|
27
|
+
| --- | --- |
|
28
|
+
|
29
|
+
## External tools dependencies
|
30
|
+
|
31
|
+
None
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Secrets reader plugin: `thycotic`
|
2
|
+
|
3
|
+
The `thycotic` secrets reader plugin retrieves secrets from a [Thycotic secrets server](https://thycotic.com/products/secret-server-vdo/), using its SOAP API.
|
4
|
+
|
5
|
+
It is configured using the `secrets_from_thycotic` (see below) config DSL and uses the `thycotic` credential ID to authenticate.
|
6
|
+
|
7
|
+
## Config DSL extension
|
8
|
+
|
9
|
+
### `secrets_from_thycotic`
|
10
|
+
|
11
|
+
Define a Thycotic URL and Thycotic secret ID to fetch from a Thycotic server.
|
12
|
+
The Thycotic secret should contain a JSON file that will be retrieved locally to be used as a secrets source. The local copy will then be removed after deployment.
|
13
|
+
|
14
|
+
Can be applied to subset of nodes using the [`for_nodes` DSL method](/docs/config_dsl.md#for_nodes).
|
15
|
+
|
16
|
+
It takes the following parameters:
|
17
|
+
* **thycotic_url** (`String`): The Thycotic server URL.
|
18
|
+
* **secret_id** (`Integer`): The Thycotic secret ID containing the secrets file to be used as secrets.
|
19
|
+
|
20
|
+
Example:
|
21
|
+
```ruby
|
22
|
+
secrets_from_thycotic(
|
23
|
+
thycotic_url: 'https://my-thycotic-server.my-domain.com/SecretServer',
|
24
|
+
secret_id: 1107
|
25
|
+
)
|
26
|
+
```
|
27
|
+
|
28
|
+
## Used credentials
|
29
|
+
|
30
|
+
| Credential | Usage
|
31
|
+
| --- | --- |
|
32
|
+
| `thycotic` | Used to authenticate on the Thycotic server's SOAP API |
|
33
|
+
|
34
|
+
## Used Metadata
|
35
|
+
|
36
|
+
| Metadata | Type | Usage
|
37
|
+
| --- | --- | --- |
|
38
|
+
|
39
|
+
## Used environment variables
|
40
|
+
|
41
|
+
| Variable | Usage
|
42
|
+
| --- | --- |
|
43
|
+
|
44
|
+
## External tools dependencies
|
45
|
+
|
46
|
+
None
|
@@ -12,7 +12,7 @@ Define a Bitbucket installation to be targeted.
|
|
12
12
|
It takes the following parameters:
|
13
13
|
* **url** (`String`): URL to the Bitbucket server
|
14
14
|
* **project** (`String`): Project name from the Bitbucket server, storing repositories
|
15
|
-
* **repos** (`Array<String>` or `Symbol`): List of repository names from this project, or
|
15
|
+
* **repos** (`Array<String>` or `Symbol`): List of repository names from this project, or `:all` for all [default: `:all`]
|
16
16
|
* **checks** (`Hash<Symbol, Object>`): Checks definition to be perform on those repositories [default: {}]
|
17
17
|
* **branch_permissions** (`Array< Hash<Symbol, Object> >`): List of branch permissions to check [optional]
|
18
18
|
* **type** (`String`): Type of branch permissions to check. Examples of values are 'fast-forward-only', 'no-deletes', 'pull-request-only'.
|
@@ -49,7 +49,7 @@ end
|
|
49
49
|
|
50
50
|
| Metadata | Type | Usage
|
51
51
|
| --- | --- | --- |
|
52
|
-
| `root_access_allowed` | `
|
52
|
+
| `root_access_allowed` | `Boolean` | If set to `true`, then skip the test for `root` access being disabled after deployment |
|
53
53
|
|
54
54
|
## Used environment variables
|
55
55
|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Test plugin: `github_ci`
|
2
|
+
|
3
|
+
The `github_ci` test plugin checks that the `master` branch of Github repositories has a successful CI result from its [Github Actions](https://github.com/features/actions).
|
4
|
+
|
5
|
+
## Config DSL extension
|
6
|
+
|
7
|
+
### `github_repos`
|
8
|
+
|
9
|
+
Define Github repositories to be targeted.
|
10
|
+
|
11
|
+
It takes the following parameters:
|
12
|
+
* **url** (`String`): URL to the Github API [default: `'https://api.github.com'`]
|
13
|
+
* **user** (`String`): User or organization name, storing repositories
|
14
|
+
* **repos** (`Array<String>` or `Symbol`): List of repository names from this project, or `:all` for all [default: `:all`]
|
15
|
+
|
16
|
+
Example:
|
17
|
+
```ruby
|
18
|
+
github_repos(
|
19
|
+
# Github's user containing repositories
|
20
|
+
user: 'My-Github-User',
|
21
|
+
# List of repositories to check
|
22
|
+
repos: [
|
23
|
+
'my-platform-repo',
|
24
|
+
'my-chef-repo',
|
25
|
+
'my-hpc-plugins'
|
26
|
+
]
|
27
|
+
)
|
28
|
+
```
|
29
|
+
|
30
|
+
## Used credentials
|
31
|
+
|
32
|
+
| Credential | Usage
|
33
|
+
| --- | --- |
|
34
|
+
| `github` | Used to connect to the Github API. Password should be the Github API token. |
|
35
|
+
|
36
|
+
## Used Metadata
|
37
|
+
|
38
|
+
| Metadata | Type | Usage
|
39
|
+
| --- | --- | --- |
|
40
|
+
|
41
|
+
## Used environment variables
|
42
|
+
|
43
|
+
| Variable | Usage
|
44
|
+
| --- | --- |
|
45
|
+
|
46
|
+
## External tools dependencies
|
47
|
+
|
48
|
+
None
|
data/docs/plugins/test/ip.md
CHANGED
@@ -16,6 +16,7 @@ None
|
|
16
16
|
|
17
17
|
| Metadata | Type | Usage
|
18
18
|
| --- | --- | --- |
|
19
|
+
| `local_node` | `Boolean` | Skip this test for nodes having this metadata set to `true` |
|
19
20
|
| `private_ips` | `Array<String>` | List of possible private IPs the node should have |
|
20
21
|
|
21
22
|
## Used environment variables
|
@@ -12,7 +12,7 @@ It takes the following parameters:
|
|
12
12
|
* **url** (`String`): URL to the Bitbucket server
|
13
13
|
* **project** (`String`): Project name from the Bitbucket server, storing repositories
|
14
14
|
* **jenkins_ci_url** (`String` or `nil`): Corresponding Jenkins CI URL, or nil if none.
|
15
|
-
* **repos** (`Array<String>` or `Symbol`): List of repository names from this project, or
|
15
|
+
* **repos** (`Array<String>` or `Symbol`): List of repository names from this project, or `:all` for all [default: `:all`]
|
16
16
|
|
17
17
|
Example:
|
18
18
|
```ruby
|
@@ -12,7 +12,7 @@ It takes the following parameters:
|
|
12
12
|
* **url** (`String`): URL to the Bitbucket server
|
13
13
|
* **project** (`String`): Project name from the Bitbucket server, storing repositories
|
14
14
|
* **jenkins_ci_url** (`String` or `nil`): Corresponding Jenkins CI URL, or nil if none.
|
15
|
-
* **repos** (`Array<String>` or `Symbol`): List of repository names from this project, or
|
15
|
+
* **repos** (`Array<String>` or `Symbol`): List of repository names from this project, or `:all` for all [default: `:all`]
|
16
16
|
|
17
17
|
Example:
|
18
18
|
```ruby
|
data/docs/plugins/test/mounts.md
CHANGED
data/docs/plugins/test/ports.md
CHANGED
@@ -39,6 +39,7 @@ check_closed_ports 25, 110
|
|
39
39
|
| Metadata | Type | Usage
|
40
40
|
| --- | --- | --- |
|
41
41
|
| `host_ip` | `String` | Host IP address to be tested for port listening |
|
42
|
+
| `local_node` | `Boolean` | Skip this test for nodes having this metadata set to `true` |
|
42
43
|
|
43
44
|
## Used environment variables
|
44
45
|
|
@@ -54,6 +54,7 @@ None
|
|
54
54
|
| Metadata | Type | Usage
|
55
55
|
| --- | --- | --- |
|
56
56
|
| `image` | `String` | The name of the OS image to be used. The [configuration](../../config_dsl.md) should define the image and point it to a directory containing a `oval.json` that will contain definition of OVAL files to be checked for this OS(see above). |
|
57
|
+
| `local_node` | `Boolean` | Skip this test for nodes having this metadata set to `true` |
|
57
58
|
|
58
59
|
## Used environment variables
|
59
60
|
|
@@ -102,7 +102,14 @@ module HybridPlatformsConductor
|
|
102
102
|
# * *progress_name* (String): Name to display on the progress bar [default: 'Executing actions']
|
103
103
|
# Result::
|
104
104
|
# * Hash<String, [Integer or Symbol, String, String]>: Exit status code (or Symbol in case of error or dry run), standard output and error for each node.
|
105
|
-
def execute_actions(
|
105
|
+
def execute_actions(
|
106
|
+
actions_per_nodes,
|
107
|
+
timeout: nil,
|
108
|
+
concurrent: false,
|
109
|
+
log_to_dir: "#{@config.hybrid_platforms_dir}/run_logs",
|
110
|
+
log_to_stdout: true,
|
111
|
+
progress_name: 'Executing actions'
|
112
|
+
)
|
106
113
|
# Keep a list of nodes that will need remote access
|
107
114
|
nodes_needing_connectors = []
|
108
115
|
# Compute the ordered list of actions per selected node
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
require 'hybrid_platforms_conductor/credentials'
|
3
|
+
|
4
|
+
module HybridPlatformsConductor
|
5
|
+
|
6
|
+
module CommonConfigDsl
|
7
|
+
|
8
|
+
module Github
|
9
|
+
|
10
|
+
# Initialize the DSL
|
11
|
+
def init_github
|
12
|
+
# List of Github repositories definitions
|
13
|
+
# Array< Hash<Symbol, Object> >
|
14
|
+
# Each definition is just mapping the signature of #github_repos
|
15
|
+
@github_repos = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Register new Github repositories
|
19
|
+
#
|
20
|
+
# Parameters::
|
21
|
+
# * *url* (String): URL to the Github API [default: 'https://api.github.com']
|
22
|
+
# * *user* (String): User or organization name, storing repositories
|
23
|
+
# * *repos* (Array<String> or Symbol): List of repository names from this project, or :all for all [default: :all]
|
24
|
+
def github_repos(url: 'https://api.github.com', user:, repos: :all)
|
25
|
+
@github_repos << {
|
26
|
+
url: url,
|
27
|
+
user: user,
|
28
|
+
repos: repos
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Iterate over each Github repository
|
33
|
+
#
|
34
|
+
# Parameters::
|
35
|
+
# * Proc: Code called for each Github repository:
|
36
|
+
# * Parameters::
|
37
|
+
# * *github* (Octokit::Client): The client instance accessing the Github API
|
38
|
+
# * *repo_info* (Hash<Symbol, Object>): The repository info:
|
39
|
+
# * *name* (String): Repository name.
|
40
|
+
# * *slug* (String): Repository slug.
|
41
|
+
def for_each_github_repo
|
42
|
+
@github_repos.each do |repo_info|
|
43
|
+
Octokit.configure do |c|
|
44
|
+
c.api_endpoint = repo_info[:url]
|
45
|
+
end
|
46
|
+
Credentials.with_credentials_for(:github, @logger, @logger_stderr, url: repo_info[:url]) do |_github_user, github_token|
|
47
|
+
client = Octokit::Client.new(access_token: github_token)
|
48
|
+
(repo_info[:repos] == :all ? client.repositories(repo_info[:user]).map { |repo| repo[:name] } : repo_info[:repos]).each do |name|
|
49
|
+
yield client, {
|
50
|
+
name: name,
|
51
|
+
slug: "#{repo_info[:user]}/#{name}"
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -11,7 +11,6 @@ require 'hybrid_platforms_conductor/logger_helpers'
|
|
11
11
|
require 'hybrid_platforms_conductor/nodes_handler'
|
12
12
|
require 'hybrid_platforms_conductor/services_handler'
|
13
13
|
require 'hybrid_platforms_conductor/plugins'
|
14
|
-
require 'hybrid_platforms_conductor/thycotic'
|
15
14
|
|
16
15
|
module HybridPlatformsConductor
|
17
16
|
|
@@ -21,12 +20,26 @@ module HybridPlatformsConductor
|
|
21
20
|
# Extend the Config DSL
|
22
21
|
module ConfigDSLExtension
|
23
22
|
|
23
|
+
# List of log plugins. Each info has the following properties:
|
24
|
+
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule.
|
25
|
+
# * *log_plugins* (Array<Symbol>): List of log plugins to be used to store deployment logs.
|
26
|
+
# Array< Hash<Symbol, Object> >
|
27
|
+
attr_reader :deployment_logs
|
28
|
+
|
29
|
+
# List of secrets reader plugins. Each info has the following properties:
|
30
|
+
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule.
|
31
|
+
# * *secrets_readers* (Array<Symbol>): List of log plugins to be used to store deployment logs.
|
32
|
+
# Array< Hash<Symbol, Object> >
|
33
|
+
attr_reader :secrets_readers
|
34
|
+
|
24
35
|
# Integer: Timeout (in seconds) for packaging repositories
|
25
36
|
attr_reader :packaging_timeout_secs
|
26
37
|
|
27
38
|
# Mixin initializer
|
28
39
|
def init_deployer_config
|
29
40
|
@packaging_timeout_secs = 60
|
41
|
+
@deployment_logs = []
|
42
|
+
@secrets_readers = []
|
30
43
|
end
|
31
44
|
|
32
45
|
# Set the packaging timeout
|
@@ -37,6 +50,28 @@ module HybridPlatformsConductor
|
|
37
50
|
@packaging_timeout_secs = packaging_timeout_secs
|
38
51
|
end
|
39
52
|
|
53
|
+
# Set the deployment log plugins to be used
|
54
|
+
#
|
55
|
+
# Parameters::
|
56
|
+
# * *log_plugins* (Symbol or Array<Symbol>): The list of (or single) log plugins to be used
|
57
|
+
def send_logs_to(*log_plugins)
|
58
|
+
@deployment_logs << {
|
59
|
+
nodes_selectors_stack: current_nodes_selectors_stack,
|
60
|
+
log_plugins: log_plugins.flatten
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set the secrets readers
|
65
|
+
#
|
66
|
+
# Parameters::
|
67
|
+
# * *secrets_readers* (Symbol or Array<Symbol>): The list of (or single) secrets readers plugins to be used
|
68
|
+
def read_secrets_from(*secrets_readers)
|
69
|
+
@secrets_readers << {
|
70
|
+
nodes_selectors_stack: current_nodes_selectors_stack,
|
71
|
+
secrets_readers: secrets_readers.flatten
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
40
75
|
end
|
41
76
|
|
42
77
|
include LoggerHelpers
|
@@ -55,10 +90,6 @@ module HybridPlatformsConductor
|
|
55
90
|
# Boolean
|
56
91
|
attr_accessor :concurrent_execution
|
57
92
|
|
58
|
-
# The list of JSON secrets
|
59
|
-
# Array<Hash>
|
60
|
-
attr_accessor :secrets
|
61
|
-
|
62
93
|
# Are we deploying in a local environment?
|
63
94
|
# Boolean
|
64
95
|
attr_accessor :local_environment
|
@@ -92,8 +123,36 @@ module HybridPlatformsConductor
|
|
92
123
|
@nodes_handler = nodes_handler
|
93
124
|
@actions_executor = actions_executor
|
94
125
|
@services_handler = services_handler
|
95
|
-
@
|
126
|
+
@override_secrets = nil
|
127
|
+
@secrets_readers = Plugins.new(
|
128
|
+
:secrets_reader,
|
129
|
+
logger: @logger,
|
130
|
+
logger_stderr: @logger_stderr,
|
131
|
+
init_plugin: proc do |plugin_class|
|
132
|
+
plugin_class.new(
|
133
|
+
logger: @logger,
|
134
|
+
logger_stderr: @logger_stderr,
|
135
|
+
config: @config,
|
136
|
+
cmd_runner: @cmd_runner,
|
137
|
+
nodes_handler: @nodes_handler
|
138
|
+
)
|
139
|
+
end
|
140
|
+
)
|
96
141
|
@provisioners = Plugins.new(:provisioner, logger: @logger, logger_stderr: @logger_stderr)
|
142
|
+
@log_plugins = Plugins.new(
|
143
|
+
:log,
|
144
|
+
logger: @logger,
|
145
|
+
logger_stderr: @logger_stderr,
|
146
|
+
init_plugin: proc do |plugin_class|
|
147
|
+
plugin_class.new(
|
148
|
+
logger: @logger,
|
149
|
+
logger_stderr: @logger_stderr,
|
150
|
+
config: @config,
|
151
|
+
nodes_handler: @nodes_handler,
|
152
|
+
actions_executor: @actions_executor
|
153
|
+
)
|
154
|
+
end
|
155
|
+
)
|
97
156
|
# Default values
|
98
157
|
@use_why_run = false
|
99
158
|
@timeout = nil
|
@@ -112,30 +171,6 @@ module HybridPlatformsConductor
|
|
112
171
|
def options_parse(options_parser, parallel_switch: true, why_run_switch: false, timeout_options: true)
|
113
172
|
options_parser.separator ''
|
114
173
|
options_parser.separator 'Deployer options:'
|
115
|
-
options_parser.on(
|
116
|
-
'-e', '--secrets SECRETS_LOCATION',
|
117
|
-
'Specify a secrets location. Can be specified several times. Location can be:',
|
118
|
-
'* Local path to a JSON file',
|
119
|
-
'* URL of the form http[s]://<url>:<secret_id> to get a secret JSON file from a Thycotic Secret Server at the given URL.'
|
120
|
-
) do |secrets_location|
|
121
|
-
@secrets << JSON.parse(
|
122
|
-
if secrets_location =~ /^(https?:\/\/.+):(\d+)$/
|
123
|
-
url = $1
|
124
|
-
secret_id = $2
|
125
|
-
secret = nil
|
126
|
-
Thycotic.with_thycotic(url, @logger, @logger_stderr) do |thycotic|
|
127
|
-
secret_file_item_id = thycotic.get_secret(secret_id).dig(:secret, :items, :secret_item, :id)
|
128
|
-
raise "Unable to fetch secret file ID #{secrets_location}" if secret_file_item_id.nil?
|
129
|
-
secret = thycotic.download_file_attachment_by_item_id(secret_id, secret_file_item_id)
|
130
|
-
raise "Unable to fetch secret file attachment from #{secrets_location}" if secret.nil?
|
131
|
-
end
|
132
|
-
secret
|
133
|
-
else
|
134
|
-
raise "Missing secret file: #{secrets_location}" unless File.exist?(secrets_location)
|
135
|
-
File.read(secrets_location)
|
136
|
-
end
|
137
|
-
)
|
138
|
-
end
|
139
174
|
options_parser.on('-p', '--parallel', 'Execute the commands in parallel (put the standard output in files <hybrid-platforms-dir>/run_logs/*.stdout)') do
|
140
175
|
@concurrent_execution = true
|
141
176
|
end if parallel_switch
|
@@ -148,6 +183,14 @@ module HybridPlatformsConductor
|
|
148
183
|
options_parser.on('--retries-on-error NBR', "Number of retries in case of non-deterministic errors (defaults to #{@nbr_retries_on_error})") do |nbr_retries|
|
149
184
|
@nbr_retries_on_error = nbr_retries.to_i
|
150
185
|
end
|
186
|
+
# Display options secrets readers might have
|
187
|
+
@secrets_readers.each do |secret_reader_name, secret_reader|
|
188
|
+
if secret_reader.respond_to?(:options_parse)
|
189
|
+
options_parser.separator ''
|
190
|
+
options_parser.separator "Secrets reader #{secret_reader_name} options:"
|
191
|
+
secret_reader.options_parse(options_parser)
|
192
|
+
end
|
193
|
+
end
|
151
194
|
end
|
152
195
|
|
153
196
|
# Validate that parsed parameters are valid
|
@@ -158,6 +201,16 @@ module HybridPlatformsConductor
|
|
158
201
|
# String: File used as a Futex for packaging
|
159
202
|
PACKAGING_FUTEX_FILE = "#{Dir.tmpdir}/hpc_packaging"
|
160
203
|
|
204
|
+
# Override the secrets with a given JSON.
|
205
|
+
# When using this method with a secrets Hash, further deployments will not query secrets readers, but will use those secrets directly.
|
206
|
+
# Useful to override secrets in test conditions when using dummy secrets for example.
|
207
|
+
#
|
208
|
+
# Parameters::
|
209
|
+
# * *secrets* (Hash or nil): Secrets to take into account in place of secrets readers, or nil to cancel a previous overriding and use secrets readers instead.
|
210
|
+
def override_secrets(secrets)
|
211
|
+
@override_secrets = secrets
|
212
|
+
end
|
213
|
+
|
161
214
|
# Deploy on a given list of nodes selectors.
|
162
215
|
# The workflow is the following:
|
163
216
|
# 1. Package the services to be deployed, considering the nodes, services and context (options, secrets, environment...)
|
@@ -177,10 +230,20 @@ module HybridPlatformsConductor
|
|
177
230
|
|
178
231
|
# Get the secrets to be deployed
|
179
232
|
secrets = {}
|
180
|
-
@
|
181
|
-
secrets
|
182
|
-
|
183
|
-
|
233
|
+
if @override_secrets
|
234
|
+
secrets = @override_secrets
|
235
|
+
else
|
236
|
+
services_to_deploy.each do |node, services|
|
237
|
+
# If there is no config for secrets, just use cli
|
238
|
+
(@config.secrets_readers.empty? ? [{ secrets_readers: %i[cli] }] : @nodes_handler.select_confs_for_node(node, @config.secrets_readers)).inject([]) do |secrets_readers, secrets_readers_info|
|
239
|
+
secrets_readers + secrets_readers_info[:secrets_readers]
|
240
|
+
end.sort.uniq.each do |secrets_reader|
|
241
|
+
services.each do |service|
|
242
|
+
node_secrets = @secrets_readers[secrets_reader].secrets_for(node, service)
|
243
|
+
conflicting_path = safe_merge(secrets, node_secrets)
|
244
|
+
raise "Secret set at path #{conflicting_path.join('->')} by #{secrets_reader} for service #{service} on node #{node} has conflicting values (#{log_debug? ? "#{node_secrets.dig(*conflicting_path)} != #{secrets.dig(*conflicting_path)}" : 'set debug for value details'})." unless conflicting_path.nil?
|
245
|
+
end
|
246
|
+
end
|
184
247
|
end
|
185
248
|
end
|
186
249
|
|
@@ -319,7 +382,7 @@ module HybridPlatformsConductor
|
|
319
382
|
)
|
320
383
|
instance.with_running_instance(stop_on_exit: true, destroy_on_exit: !reuse_instance, port: 22) do
|
321
384
|
# Test-provisioned nodes have SSH Session Exec capabilities and are not local
|
322
|
-
sub_executable.nodes_handler.override_metadata_of node, :ssh_session_exec,
|
385
|
+
sub_executable.nodes_handler.override_metadata_of node, :ssh_session_exec, true
|
323
386
|
sub_executable.nodes_handler.override_metadata_of node, :local_node, false
|
324
387
|
# Test-provisioned nodes use default sudo
|
325
388
|
sub_executable.config.sudo_procs.replace(sub_executable.config.sudo_procs.map do |sudo_proc_info|
|
@@ -338,7 +401,7 @@ module HybridPlatformsConductor
|
|
338
401
|
deployer.local_environment = true
|
339
402
|
# Ignore secrets that might have been given: in Docker containers we always use dummy secrets
|
340
403
|
dummy_secrets_file = "#{@config.hybrid_platforms_dir}/dummy_secrets.json"
|
341
|
-
deployer.
|
404
|
+
deployer.override_secrets(File.exist?(dummy_secrets_file) ? JSON.parse(File.read(dummy_secrets_file)) : {})
|
342
405
|
yield deployer, instance
|
343
406
|
end
|
344
407
|
rescue
|
@@ -355,72 +418,31 @@ module HybridPlatformsConductor
|
|
355
418
|
# * *nodes* (Array<String>): Nodes to get info from
|
356
419
|
# Result::
|
357
420
|
# * Hash<String, Hash<Symbol,Object>: The deployed info, per node name.
|
358
|
-
#
|
359
|
-
# * *
|
421
|
+
# * *error* (String): Error string in case deployment logs could not be retrieved. If set then further properties will be ignored. [optional]
|
422
|
+
# * *services* (Array<String>): List of services deployed on the node
|
423
|
+
# * *deployment_info* (Hash<Symbol,Object>): Deployment metadata
|
424
|
+
# * *exit_status* (Integer or Symbol): Deployment exit status
|
425
|
+
# * *stdout* (String): Deployment stdout
|
426
|
+
# * *stderr* (String): Deployment stderr
|
360
427
|
def deployment_info_from(*nodes)
|
428
|
+
nodes = nodes.flatten
|
361
429
|
@actions_executor.max_threads = 64
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
# ...
|
379
|
-
# PropertyN: ValueN
|
380
|
-
# ===== STDOUT =====
|
381
|
-
# ...
|
382
|
-
deploy_info = {}
|
383
|
-
if exit_status.is_a?(Symbol)
|
384
|
-
deploy_info[:error] = "Error: #{exit_status}\n#{stderr}"
|
385
|
-
else
|
386
|
-
stdout_lines = stdout.split("\n")
|
387
|
-
if stdout_lines.first =~ /No such file or directory/
|
388
|
-
deploy_info[:error] = '/var/log/deployments missing'
|
389
|
-
else
|
390
|
-
stdout_lines.each do |line|
|
391
|
-
if line =~ /^([^:]+): (.+)$/
|
392
|
-
key_str, value = $1, $2
|
393
|
-
key = key_str.to_sym
|
394
|
-
# Type-cast some values
|
395
|
-
case key_str
|
396
|
-
when 'date'
|
397
|
-
# Date and time values
|
398
|
-
# Thu Nov 23 18:43:01 UTC 2017
|
399
|
-
deploy_info[key] = Time.parse(value)
|
400
|
-
when 'debug'
|
401
|
-
# Boolean values
|
402
|
-
# Yes
|
403
|
-
deploy_info[key] = (value == 'Yes')
|
404
|
-
when /^diff_files_.+$/, 'services'
|
405
|
-
# Array of strings
|
406
|
-
# my_file.txt, other_file.txt
|
407
|
-
deploy_info[key] = value.split(', ')
|
408
|
-
else
|
409
|
-
deploy_info[key] = value
|
410
|
-
end
|
411
|
-
else
|
412
|
-
deploy_info[:unknown_lines] = [] unless deploy_info.key?(:unknown_lines)
|
413
|
-
deploy_info[:unknown_lines] << line
|
414
|
-
end
|
415
|
-
end
|
416
|
-
end
|
417
|
-
end
|
418
|
-
[
|
419
|
-
node,
|
420
|
-
deploy_info
|
421
|
-
]
|
422
|
-
end
|
423
|
-
]
|
430
|
+
read_actions_results = @actions_executor.execute_actions(
|
431
|
+
Hash[nodes.map do |node|
|
432
|
+
master_log_plugin = @log_plugins[log_plugins_for(node).first]
|
433
|
+
master_log_plugin.respond_to?(:actions_to_read_logs) ? [node, master_log_plugin.actions_to_read_logs(node)] : nil
|
434
|
+
end.compact],
|
435
|
+
log_to_stdout: false,
|
436
|
+
concurrent: true,
|
437
|
+
timeout: 10,
|
438
|
+
progress_name: 'Read deployment logs'
|
439
|
+
)
|
440
|
+
Hash[nodes.map do |node|
|
441
|
+
[
|
442
|
+
node,
|
443
|
+
@log_plugins[log_plugins_for(node).first].logs_for(node, *(read_actions_results[node] || [nil, nil, nil]))
|
444
|
+
]
|
445
|
+
end]
|
424
446
|
end
|
425
447
|
|
426
448
|
# Parse stdout and stderr of a given deploy run and get the list of tasks with their status
|
@@ -442,6 +464,35 @@ module HybridPlatformsConductor
|
|
442
464
|
|
443
465
|
private
|
444
466
|
|
467
|
+
# Safe-merge 2 hashes.
|
468
|
+
# Safe-merging is done by:
|
469
|
+
# * Merging values that are hashes.
|
470
|
+
# * Reporting errors when values conflict.
|
471
|
+
# When values are conflicting, the initial hash won't modify those conflicting values and will stop the merge.
|
472
|
+
#
|
473
|
+
# Parameters::
|
474
|
+
# * *hash* (Hash): Hash to be modified merging hash_to_merge
|
475
|
+
# * *hash_to_merge* (Hash): Hash to be merged into hash
|
476
|
+
# Result::
|
477
|
+
# * nil or Array<Object>: nil in case of success, or the keys path leading to a conflicting value in case of error
|
478
|
+
def safe_merge(hash, hash_to_merge)
|
479
|
+
conflicting_path = nil
|
480
|
+
hash_to_merge.each do |key, value_to_merge|
|
481
|
+
if hash.key?(key)
|
482
|
+
if hash[key].is_a?(Hash) && value_to_merge.is_a?(Hash)
|
483
|
+
sub_conflicting_path = safe_merge(hash[key], value_to_merge)
|
484
|
+
conflicting_path = [key] + sub_conflicting_path unless sub_conflicting_path.nil?
|
485
|
+
elsif hash[key] != value_to_merge
|
486
|
+
conflicting_path = [key]
|
487
|
+
end
|
488
|
+
else
|
489
|
+
hash[key] = value_to_merge
|
490
|
+
end
|
491
|
+
break unless conflicting_path.nil?
|
492
|
+
end
|
493
|
+
conflicting_path
|
494
|
+
end
|
495
|
+
|
445
496
|
# Get the list of retriable errors a node got from deployment logs.
|
446
497
|
# Useful to know if an error is non-deterministic (due to external and temporary factors).
|
447
498
|
#
|
@@ -494,7 +545,7 @@ module HybridPlatformsConductor
|
|
494
545
|
Hash[services.map do |node, node_services|
|
495
546
|
image_id = @nodes_handler.get_image_of(node)
|
496
547
|
sudo = (ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} ")
|
497
|
-
# Install
|
548
|
+
# Install corporate certificates if present
|
498
549
|
certificate_actions =
|
499
550
|
if @local_environment && ENV['hpc_certificates']
|
500
551
|
if File.exist?(ENV['hpc_certificates'])
|
@@ -584,47 +635,48 @@ module HybridPlatformsConductor
|
|
584
635
|
# * *services* (Hash<String, Array<String>>): List of services that have been deployed, per node
|
585
636
|
def save_logs(logs, services)
|
586
637
|
section "Saving deployment logs for #{logs.size} nodes" do
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
end
|
638
|
+
ssh_user = @actions_executor.connector(:ssh).ssh_user
|
639
|
+
@actions_executor.execute_actions(
|
640
|
+
Hash[logs.map do |node, (exit_status, stdout, stderr)|
|
641
|
+
[
|
642
|
+
node,
|
643
|
+
log_plugins_for(node).
|
644
|
+
map do |log_plugin|
|
645
|
+
@log_plugins[log_plugin].actions_to_save_logs(
|
646
|
+
node,
|
647
|
+
services[node],
|
648
|
+
@services_handler.log_info_for(node, services[node]).merge(
|
649
|
+
date: Time.now.utc.strftime('%F %T'),
|
650
|
+
user: ssh_user
|
651
|
+
),
|
652
|
+
exit_status,
|
653
|
+
stdout,
|
654
|
+
stderr
|
655
|
+
)
|
656
|
+
end.
|
657
|
+
flatten(1)
|
658
|
+
]
|
659
|
+
end],
|
660
|
+
timeout: 10,
|
661
|
+
concurrent: true,
|
662
|
+
log_to_dir: nil,
|
663
|
+
progress_name: 'Saving logs'
|
664
|
+
)
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
# Get the list of log plugins to be used for a given node
|
669
|
+
#
|
670
|
+
# Parameters::
|
671
|
+
# * *node* (String): The node for which log plugins are queried
|
672
|
+
# Result::
|
673
|
+
# * Array<Symbol>: The list of log plugins
|
674
|
+
def log_plugins_for(node)
|
675
|
+
node_log_plugins = @nodes_handler.select_confs_for_node(node, @config.deployment_logs).inject([]) do |log_plugins, deployment_logs_info|
|
676
|
+
log_plugins + deployment_logs_info[:log_plugins]
|
627
677
|
end
|
678
|
+
node_log_plugins << :remote_fs if node_log_plugins.empty?
|
679
|
+
node_log_plugins
|
628
680
|
end
|
629
681
|
|
630
682
|
end
|