hybrid_platforms_conductor 32.16.3 → 33.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +6 -3
- data/bin/last_deploys +4 -1
- data/bin/nodes_to_deploy +5 -5
- data/docs/config_dsl.md +45 -1
- data/docs/executables.md +6 -7
- data/docs/executables/check-node.md +3 -3
- data/docs/executables/deploy.md +3 -3
- data/docs/executables/dump_nodes_json.md +3 -3
- data/docs/executables/test.md +3 -3
- data/docs/executables/topograph.md +3 -3
- data/docs/gen/mermaid/README.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/check-node.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/deploy.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/free_ips.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/get_impacted_nodes.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/last_deploys.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/nodes_to_deploy.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/report.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/run.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/ssh_config.md-0.png +0 -0
- data/docs/gen/mermaid/docs/executables/test.md-0.png +0 -0
- data/docs/plugins.md +47 -0
- data/docs/plugins/connector/ssh.md +1 -1
- data/docs/plugins/log/remote_fs.md +26 -0
- data/docs/plugins/secrets_reader/cli.md +31 -0
- data/docs/plugins/secrets_reader/thycotic.md +46 -0
- data/docs/plugins/test/bitbucket_conf.md +1 -1
- data/docs/plugins/test/check_deploy_and_idempotence.md +1 -1
- data/docs/plugins/test/connection.md +1 -0
- data/docs/plugins/test/deploy_removes_root_access.md +1 -1
- data/docs/plugins/test/file_system.md +1 -0
- data/docs/plugins/test/github_ci.md +48 -0
- data/docs/plugins/test/hostname.md +1 -0
- data/docs/plugins/test/ip.md +1 -0
- data/docs/plugins/test/jenkins_ci_conf.md +1 -1
- data/docs/plugins/test/jenkins_ci_masters_ok.md +1 -1
- data/docs/plugins/test/local_users.md +1 -0
- data/docs/plugins/test/mounts.md +1 -0
- data/docs/plugins/test/orphan_files.md +1 -0
- data/docs/plugins/test/ports.md +1 -0
- data/docs/plugins/test/spectre.md +1 -0
- data/docs/plugins/test/vulnerabilities.md +1 -0
- data/lib/hybrid_platforms_conductor/actions_executor.rb +8 -1
- data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +62 -0
- data/lib/hybrid_platforms_conductor/deployer.rb +193 -141
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +3 -3
- data/lib/hybrid_platforms_conductor/hpc_plugins/log/my_log_plugin.rb.sample +100 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/log/remote_fs.rb +179 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/cli.rb +75 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/my_secrets_reader_plugin.rb.sample +46 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +87 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/connection.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_freshness.rb +7 -20
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +2 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +32 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/ports.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +2 -1
- data/lib/hybrid_platforms_conductor/log.rb +31 -0
- data/lib/hybrid_platforms_conductor/plugins.rb +1 -0
- data/lib/hybrid_platforms_conductor/secrets_reader.rb +31 -0
- data/lib/hybrid_platforms_conductor/test_only_remote_node.rb +18 -0
- data/lib/hybrid_platforms_conductor/version.rb +1 -1
- data/spec/hybrid_platforms_conductor_test.rb +27 -6
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +3 -3
- data/spec/hybrid_platforms_conductor_test/api/deployer/config_dsl_spec.rb +46 -4
- data/spec/hybrid_platforms_conductor_test/api/deployer/deploy_spec.rb +187 -212
- data/spec/hybrid_platforms_conductor_test/api/deployer/log_plugins/remote_fs_spec.rb +223 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioner_spec.rb +4 -4
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/cli_spec.rb +63 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +253 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/global_spec.rb +1 -1
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +72 -0
- data/spec/hybrid_platforms_conductor_test/executables/last_deploys_spec.rb +146 -98
- data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +240 -83
- data/spec/hybrid_platforms_conductor_test/executables/options/common_spec.rb +2 -1
- data/spec/hybrid_platforms_conductor_test/executables/options/deployer_spec.rb +0 -182
- data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +1 -1
- data/spec/hybrid_platforms_conductor_test/helpers/deployer_helpers.rb +40 -53
- data/spec/hybrid_platforms_conductor_test/helpers/deployer_test_helpers.rb +251 -15
- data/spec/hybrid_platforms_conductor_test/test_log_no_read_plugin.rb +82 -0
- data/spec/hybrid_platforms_conductor_test/test_log_plugin.rb +103 -0
- data/spec/hybrid_platforms_conductor_test/test_secrets_reader_plugin.rb +45 -0
- metadata +41 -2
|
@@ -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
|