cfengine 0.16.6__tar.gz → 0.16.7__tar.gz
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.
- {cfengine-0.16.6 → cfengine-0.16.7}/PKG-INFO +3 -3
- {cfengine-0.16.6 → cfengine-0.16.7}/README.md +2 -2
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/PKG-INFO +3 -3
- cfengine-0.16.7/src/cfengine.egg-info/scm_version.json +8 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/commands.py +41 -4
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/main.py +3 -0
- cfengine-0.16.7/src/cfengine_cli/up.py +437 -0
- cfengine-0.16.7/tests/up-validate/001_complex_config.yaml +71 -0
- cfengine-0.16.7/tests/up-validate/002_version.x.yaml +16 -0
- cfengine-0.16.7/tests/up-validate/003_box.x.yaml +16 -0
- cfengine-0.16.7/tests/up-validate/004_count.x.yaml +16 -0
- cfengine-0.16.6/src/cfengine.egg-info/scm_version.json +0 -8
- cfengine-0.16.6/src/cfengine_cli/up.py +0 -221
- cfengine-0.16.6/tests/up-validate/001_complex_config.yaml +0 -71
- cfengine-0.16.6/tests/up-validate/002_version.x.yaml +0 -16
- cfengine-0.16.6/tests/up-validate/003_box.x.yaml +0 -16
- cfengine-0.16.6/tests/up-validate/004_count.x.yaml +0 -16
- {cfengine-0.16.6 → cfengine-0.16.7}/.github/dependabot.yml +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/lint-policy-in-other-repos.yml +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/make-check.yml +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/pypi-publish.yml +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/stale.yml +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/update-syntax-description.yml +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/.gitignore +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/.python-version +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/CLAUDE.md +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/HACKING.md +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/LICENSE +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/Makefile +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/ci/01-install.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/ci/02-safe-tests.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/ci/03-unsafe-tests.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/pyproject.toml +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/setup.cfg +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/SOURCES.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/dependency_links.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/entry_points.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/requires.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/scm_file_list.json +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/top_level.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/__init__.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/__main__.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/changelog.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/deptool-README.md +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/deptool.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/dev.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/docs.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/format.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/lint.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/lint_csv.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/lint_yml.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/__init__.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/analyze.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/check_download_matches_git.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/download.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/generate_git_tags.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/generate_release_information.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/generate_vcf_download.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/generate_vcf_git_checkout.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/paths.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/profile.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/shell.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/syntax-description.json +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/syntax_tree.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/utils.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/validate.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/version.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/README.md +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/001_hello_world.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/001_hello_world.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/002_basics.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/002_basics.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/003_wrapping.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/003_wrapping.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/004_comments.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/004_comments.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/005_bundle_comments.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/005_bundle_comments.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/006_remove_empty_comments.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/006_remove_empty_comments.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/007_class_guarded_empty_lines.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/007_class_guarded_empty_lines.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/008_long_string.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/008_long_string.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/009_single_line.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/009_single_line.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/010_stakeholder.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/010_stakeholder.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/011_macros.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/011_macros.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/011_promises.expected.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/011_promises.input.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/001_hello_world.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/002_deprecations.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/002_deprecations.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/003_name_lowercase.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/003_name_lowercase.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/003_name_lowercase.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/004_block_type.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/004_block_type.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/004_block_type.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/005_syntax_error.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/005_syntax_error.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/005_syntax_error.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/006_empty_file.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/006_empty_file.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/007_namespace.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/007_namespace.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/007_namespace.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/008_bundle_shadows_function.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/008_bundle_shadows_function.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/008_bundle_shadows_function.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/009_unknown_function_inside_vars.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/009_unknown_function_inside_vars.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/009_unknown_function_inside_vars.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/010_mutually_exclusive_types_vars.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/010_mutually_exclusive_types_vars.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/010_mutually_exclusive_types_vars.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/011_invalid_attributes.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/011_invalid_attributes.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/012_function_call_arg_count.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/012_function_call_arg_count.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/012_function_call_arg_count.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_body.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_body.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_bundle.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_bundle.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_bundle_body.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/014_variadic_func_arg_count.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/014_variadic_func_arg_count.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/014_variadic_func_arg_count.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/015_macro_multi_def_bundle.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/015_macro_multi_def_bundle.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/015_macro_multi_def_bundle.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/016_half_promises.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/016_half_promises.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/016_half_promises.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/017_implies_body.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/017_implies_body.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/017_implies_body.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/018_nested_calls.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/018_nested_calls.expected.txt +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/018_nested_calls.x.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/019_bundle_name_expansion.cf +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/run-format-tests.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/run-lint-tests.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/run-shell-tests.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/run-up-validate-tests.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/001-help.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/002-version.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/003-format.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/004-format-check.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/005-lint.sh +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/__init__.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_changelog.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_deps.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_format.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_lint_csv.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_lint_yml.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_paths.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_utils.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_version.py +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/up-validate/mocks/cloud_config.json +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/tests/up-validate/mocks/vagrant +0 -0
- {cfengine-0.16.6 → cfengine-0.16.7}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cfengine
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.7
|
|
4
4
|
Summary: Human-oriented CLI for interacting with CFEngine policy language, projects, and other CFEngine tools
|
|
5
5
|
License: GNU GENERAL PUBLIC LICENSE
|
|
6
6
|
Version 3, 29 June 2007
|
|
@@ -751,13 +751,13 @@ cfengine format
|
|
|
751
751
|
cfengine lint
|
|
752
752
|
```
|
|
753
753
|
|
|
754
|
-
You can also specify filenames or folders
|
|
754
|
+
You can also specify filenames or folders:
|
|
755
755
|
|
|
756
756
|
```bash
|
|
757
757
|
cfengine lint main.cf
|
|
758
758
|
```
|
|
759
759
|
|
|
760
|
-
When it finds a mistake, it points out where the problem is like this
|
|
760
|
+
When it finds a mistake, it points out where the problem is like this:
|
|
761
761
|
|
|
762
762
|
```
|
|
763
763
|
"Hello, CFEngine"
|
|
@@ -47,13 +47,13 @@ cfengine format
|
|
|
47
47
|
cfengine lint
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
You can also specify filenames or folders
|
|
50
|
+
You can also specify filenames or folders:
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
53
|
cfengine lint main.cf
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
When it finds a mistake, it points out where the problem is like this
|
|
56
|
+
When it finds a mistake, it points out where the problem is like this:
|
|
57
57
|
|
|
58
58
|
```
|
|
59
59
|
"Hello, CFEngine"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cfengine
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.7
|
|
4
4
|
Summary: Human-oriented CLI for interacting with CFEngine policy language, projects, and other CFEngine tools
|
|
5
5
|
License: GNU GENERAL PUBLIC LICENSE
|
|
6
6
|
Version 3, 29 June 2007
|
|
@@ -751,13 +751,13 @@ cfengine format
|
|
|
751
751
|
cfengine lint
|
|
752
752
|
```
|
|
753
753
|
|
|
754
|
-
You can also specify filenames or folders
|
|
754
|
+
You can also specify filenames or folders:
|
|
755
755
|
|
|
756
756
|
```bash
|
|
757
757
|
cfengine lint main.cf
|
|
758
758
|
```
|
|
759
759
|
|
|
760
|
-
When it finds a mistake, it points out where the problem is like this
|
|
760
|
+
When it finds a mistake, it points out where the problem is like this:
|
|
761
761
|
|
|
762
762
|
```
|
|
763
763
|
"Hello, CFEngine"
|
|
@@ -10,9 +10,11 @@ from cfengine_cli.paths import bin
|
|
|
10
10
|
from cfengine_cli.version import cfengine_cli_version_string
|
|
11
11
|
from cfengine_cli.format import format_paths
|
|
12
12
|
from cfengine_cli.utils import UserError
|
|
13
|
-
from cfengine_cli.up import validate_config
|
|
13
|
+
from cfengine_cli.up import validate_config, up_do, resolve_templates
|
|
14
14
|
from cfbs.commands import build_command
|
|
15
15
|
from cf_remote.commands import deploy as deploy_command
|
|
16
|
+
from cf_remote.paths import cf_remote_dir
|
|
17
|
+
from pydantic import ValidationError
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
def _require_cfagent():
|
|
@@ -108,12 +110,47 @@ def up(args) -> int:
|
|
|
108
110
|
with open(args.config, "r") as f:
|
|
109
111
|
content = yaml.safe_load(f)
|
|
110
112
|
except yaml.YAMLError:
|
|
111
|
-
raise UserError("'%s' is not
|
|
113
|
+
raise UserError("'%s' is not valid yaml" % args.config)
|
|
112
114
|
except FileNotFoundError:
|
|
113
115
|
raise UserError("'%s' doesn't exist" % args.config)
|
|
114
116
|
|
|
115
|
-
|
|
117
|
+
new_group = resolve_templates(content)
|
|
118
|
+
try:
|
|
119
|
+
new_config = validate_config(new_group)
|
|
120
|
+
except ValidationError as v:
|
|
121
|
+
msgs = []
|
|
122
|
+
for err in v.errors():
|
|
123
|
+
msgs.append(
|
|
124
|
+
f"{err['msg']}. Input '{err['input']}' at location '{err['loc']}'"
|
|
125
|
+
)
|
|
126
|
+
raise UserError("\n".join(msgs))
|
|
127
|
+
|
|
116
128
|
if args.validate:
|
|
117
129
|
return 0
|
|
118
|
-
|
|
130
|
+
|
|
131
|
+
old_group = {}
|
|
132
|
+
try:
|
|
133
|
+
if not args.reset:
|
|
134
|
+
with open(os.path.join(cf_remote_dir(), "old_groups.yaml"), "r") as f:
|
|
135
|
+
old_group = yaml.safe_load(f)
|
|
136
|
+
else:
|
|
137
|
+
os.remove(os.path.join(cf_remote_dir(), "old_groups.yaml"))
|
|
138
|
+
except:
|
|
139
|
+
pass
|
|
140
|
+
if old_group != {}:
|
|
141
|
+
try:
|
|
142
|
+
validate_config(old_group)
|
|
143
|
+
except ValidationError as v:
|
|
144
|
+
msgs = []
|
|
145
|
+
for err in v.errors():
|
|
146
|
+
msgs.append(
|
|
147
|
+
f"{err['msg']}. Input '{err['input']}' at location '{err['loc']}'"
|
|
148
|
+
)
|
|
149
|
+
raise UserError("\n".join(msgs))
|
|
150
|
+
|
|
151
|
+
is_ok = up_do(old_group, new_group, new_config)
|
|
152
|
+
|
|
153
|
+
if is_ok:
|
|
154
|
+
with open(os.path.join(cf_remote_dir(), "old_groups.yaml"), "w") as f:
|
|
155
|
+
yaml.dump(new_group, f, default_flow_style=False, sort_keys=False)
|
|
119
156
|
return 0
|
|
@@ -126,6 +126,9 @@ def _get_arg_parser():
|
|
|
126
126
|
up_parser.add_argument(
|
|
127
127
|
"--validate", action="store_true", help="Validate the given config"
|
|
128
128
|
)
|
|
129
|
+
up_parser.add_argument(
|
|
130
|
+
"--reset", action="store_true", help="Create a fresh new environment"
|
|
131
|
+
)
|
|
129
132
|
parser = dev_subparsers.add_parser(
|
|
130
133
|
"generate-changelog",
|
|
131
134
|
description="""Changelog generator for CFEngine repositories.
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
from pydantic import BaseModel, model_validator, Field
|
|
2
|
+
from typing import Union, Literal, Optional, List, Annotated
|
|
3
|
+
|
|
4
|
+
import cfengine_cli.validate as validate
|
|
5
|
+
from cfengine_cli.utils import UserError
|
|
6
|
+
|
|
7
|
+
from cf_remote import log
|
|
8
|
+
from cf_remote import commands
|
|
9
|
+
from cf_remote import utils
|
|
10
|
+
from cf_remote import paths
|
|
11
|
+
from cf_remote import aramid
|
|
12
|
+
|
|
13
|
+
import textwrap
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Forces pydantic to throw validation error if config contains unknown keys
|
|
20
|
+
class NoExtra(BaseModel, extra="forbid"):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Config(NoExtra):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AWSConfig(Config):
|
|
29
|
+
image: str
|
|
30
|
+
size: Optional[Literal["micro", "xlarge"]] = None
|
|
31
|
+
region: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
@model_validator(mode="after")
|
|
34
|
+
def check_aws_config(self):
|
|
35
|
+
validate.validate_aws_image(self.image)
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class VagrantConfig(Config):
|
|
40
|
+
box: str
|
|
41
|
+
memory: int = 512
|
|
42
|
+
cpus: int = 1
|
|
43
|
+
sync_folder: Optional[str] = None
|
|
44
|
+
provision: Optional[str] = None
|
|
45
|
+
|
|
46
|
+
@model_validator(mode="after")
|
|
47
|
+
def check_vagrant_config(self):
|
|
48
|
+
if self.memory < 512:
|
|
49
|
+
raise UserError("Cannot allocate less than 512MB to a Vagrant VM")
|
|
50
|
+
if self.cpus < 1:
|
|
51
|
+
raise UserError("Cannot use less than 1 cpu per Vagrant VM")
|
|
52
|
+
|
|
53
|
+
validate.validate_vagrant_box(self.box)
|
|
54
|
+
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class GCPConfig(Config):
|
|
59
|
+
image: str # There is no list of available GCP platforms to validate against yet
|
|
60
|
+
network: Optional[str] = None
|
|
61
|
+
public_ip: bool = True
|
|
62
|
+
size: Optional[str] = None
|
|
63
|
+
region: Optional[str] = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class AWSProvider(Config):
|
|
67
|
+
provider: Literal["aws"]
|
|
68
|
+
aws: AWSConfig
|
|
69
|
+
|
|
70
|
+
@model_validator(mode="after")
|
|
71
|
+
def check_aws_provider(self):
|
|
72
|
+
validate.validate_aws_credentials()
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class GCPProvider(Config):
|
|
77
|
+
provider: Literal["gcp"]
|
|
78
|
+
gcp: GCPConfig
|
|
79
|
+
|
|
80
|
+
@model_validator(mode="after")
|
|
81
|
+
def check_gcp_provider(self):
|
|
82
|
+
validate.validate_gcp_credentials()
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class VagrantProvider(Config):
|
|
87
|
+
provider: Literal["vagrant"]
|
|
88
|
+
vagrant: VagrantConfig
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class SaveMode(Config):
|
|
92
|
+
mode: Literal["save"]
|
|
93
|
+
hosts: List[str]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class SpawnMode(Config):
|
|
97
|
+
mode: Literal["spawn"]
|
|
98
|
+
# "Field" forces pydantic to report errors on the branch defined by the field "mode"
|
|
99
|
+
spawn: Annotated[
|
|
100
|
+
Union[VagrantProvider, AWSProvider, GCPProvider],
|
|
101
|
+
Field(discriminator="provider"),
|
|
102
|
+
]
|
|
103
|
+
count: int
|
|
104
|
+
|
|
105
|
+
@model_validator(mode="after")
|
|
106
|
+
def check_spawn_config(self):
|
|
107
|
+
if self.count < 1:
|
|
108
|
+
raise UserError("Cannot spawn less than 1 instance")
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class CFEngineConfig(Config):
|
|
113
|
+
version: Optional[str] = None
|
|
114
|
+
bootstrap: Optional[str] = None
|
|
115
|
+
edition: Literal["community", "enterprise"] = "enterprise"
|
|
116
|
+
remote_download: bool = False
|
|
117
|
+
hub_package: Optional[str] = None
|
|
118
|
+
client_package: Optional[str] = None
|
|
119
|
+
package: Optional[str] = None
|
|
120
|
+
demo: bool = False
|
|
121
|
+
insecure: bool = False
|
|
122
|
+
call_collect: bool = False
|
|
123
|
+
trust_keys: Optional[List[str]] = None
|
|
124
|
+
|
|
125
|
+
@model_validator(mode="after")
|
|
126
|
+
def check_cfengine_config(self):
|
|
127
|
+
packages = [self.package, self.hub_package, self.client_package]
|
|
128
|
+
for p in packages:
|
|
129
|
+
validate.validate_package(p, self.remote_download)
|
|
130
|
+
|
|
131
|
+
if self.version and any(packages):
|
|
132
|
+
log.warning("Specifying package overrides cfengine version")
|
|
133
|
+
|
|
134
|
+
validate.validate_version(self.version, self.edition)
|
|
135
|
+
validate.validate_state_bootstrap(self.bootstrap)
|
|
136
|
+
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class GroupConfig(Config):
|
|
141
|
+
role: Literal["client", "hub"]
|
|
142
|
+
# "Field" forces pydantic to report errors on the branch defined by the field "provider"
|
|
143
|
+
source: Annotated[Union[SaveMode, SpawnMode], Field(discriminator="mode")]
|
|
144
|
+
cfengine: Optional[CFEngineConfig] = None
|
|
145
|
+
scripts: Optional[List[str]] = None
|
|
146
|
+
|
|
147
|
+
@model_validator(mode="after")
|
|
148
|
+
def check_group_config(self):
|
|
149
|
+
if (
|
|
150
|
+
self.role == "hub"
|
|
151
|
+
and self.source.mode == "spawn"
|
|
152
|
+
and self.source.count != 1
|
|
153
|
+
):
|
|
154
|
+
raise UserError("A hub can only have one host")
|
|
155
|
+
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _resolve_templates(parent, templates):
|
|
160
|
+
if not parent:
|
|
161
|
+
return
|
|
162
|
+
if isinstance(parent, dict):
|
|
163
|
+
for key, value in parent.items():
|
|
164
|
+
if isinstance(value, str) and value in templates:
|
|
165
|
+
parent[key] = templates[value]
|
|
166
|
+
else:
|
|
167
|
+
_resolve_templates(value, templates)
|
|
168
|
+
if isinstance(parent, list):
|
|
169
|
+
for value in parent:
|
|
170
|
+
_resolve_templates(value, templates)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def resolve_templates(content):
|
|
174
|
+
if not content:
|
|
175
|
+
raise UserError("Empty spawn config")
|
|
176
|
+
|
|
177
|
+
if "groups" not in content:
|
|
178
|
+
raise UserError("Missing 'groups' key in spawn config")
|
|
179
|
+
|
|
180
|
+
groups = content["groups"]
|
|
181
|
+
if groups is None:
|
|
182
|
+
return {}
|
|
183
|
+
|
|
184
|
+
templates = content.get("templates")
|
|
185
|
+
if templates:
|
|
186
|
+
_resolve_templates(groups, templates)
|
|
187
|
+
|
|
188
|
+
return groups
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def validate_config(groups):
|
|
192
|
+
|
|
193
|
+
state = {}
|
|
194
|
+
for k, v in groups.items():
|
|
195
|
+
state[k] = GroupConfig(**v)
|
|
196
|
+
|
|
197
|
+
return state
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def generate_diff(old_state, new_state):
|
|
201
|
+
spawn = []
|
|
202
|
+
destroy = []
|
|
203
|
+
install = []
|
|
204
|
+
uninstall = []
|
|
205
|
+
|
|
206
|
+
to_print = defaultdict(list)
|
|
207
|
+
|
|
208
|
+
for key in old_state.keys() | new_state.keys():
|
|
209
|
+
if key in old_state.keys() & new_state.keys():
|
|
210
|
+
if old_state[key]["source"] != new_state[key]["source"]:
|
|
211
|
+
destroy.append(key)
|
|
212
|
+
spawn.append(key)
|
|
213
|
+
|
|
214
|
+
old_text = textwrap.indent(
|
|
215
|
+
yaml.dump({"source": old_state[key]["source"]}), "- "
|
|
216
|
+
)
|
|
217
|
+
new_text = textwrap.indent(
|
|
218
|
+
yaml.dump({"source": new_state[key]["source"]}), "+ "
|
|
219
|
+
)
|
|
220
|
+
to_print[key].append(old_text + new_text)
|
|
221
|
+
|
|
222
|
+
if old_state[key]["role"] != new_state[key]["role"]:
|
|
223
|
+
if key not in destroy:
|
|
224
|
+
uninstall.append(key)
|
|
225
|
+
install.append(key)
|
|
226
|
+
|
|
227
|
+
old_text = textwrap.indent(
|
|
228
|
+
yaml.dump({"role": old_state[key]["role"]}), "- "
|
|
229
|
+
)
|
|
230
|
+
new_text = textwrap.indent(
|
|
231
|
+
yaml.dump({"role": new_state[key]["role"]}), "+ "
|
|
232
|
+
)
|
|
233
|
+
to_print[key].append(old_text + new_text)
|
|
234
|
+
|
|
235
|
+
if old_state[key].get("cfengine", None) != new_state[key].get(
|
|
236
|
+
"cfengine", None
|
|
237
|
+
):
|
|
238
|
+
if key not in destroy:
|
|
239
|
+
uninstall.append(key)
|
|
240
|
+
install.append(key)
|
|
241
|
+
|
|
242
|
+
old_text = (
|
|
243
|
+
textwrap.indent(
|
|
244
|
+
yaml.dump({"cfengine": old_state[key]["cfengine"]}), "- "
|
|
245
|
+
)
|
|
246
|
+
if "cfengine" in old_state[key]
|
|
247
|
+
else ""
|
|
248
|
+
)
|
|
249
|
+
new_text = (
|
|
250
|
+
textwrap.indent(
|
|
251
|
+
yaml.dump({"cfengine": new_state[key]["cfengine"]}), "+ "
|
|
252
|
+
)
|
|
253
|
+
if "cfengine" in old_state
|
|
254
|
+
else ""
|
|
255
|
+
)
|
|
256
|
+
to_print[key].append(old_text + new_text)
|
|
257
|
+
|
|
258
|
+
elif key not in old_state:
|
|
259
|
+
spawn.append(key)
|
|
260
|
+
if "cfengine" in new_state[key]:
|
|
261
|
+
install.append(key)
|
|
262
|
+
|
|
263
|
+
new_text = textwrap.indent(yaml.dump(new_state[key]), "+ ")
|
|
264
|
+
to_print[key].append(new_text)
|
|
265
|
+
elif key not in new_state:
|
|
266
|
+
destroy.append(key)
|
|
267
|
+
|
|
268
|
+
old_text = textwrap.indent(yaml.dump(old_state[key]), "- ")
|
|
269
|
+
to_print[key].append(old_text)
|
|
270
|
+
|
|
271
|
+
for group_name, changes in to_print.items():
|
|
272
|
+
print(f"{group_name}:")
|
|
273
|
+
for change in changes:
|
|
274
|
+
print(change)
|
|
275
|
+
|
|
276
|
+
return spawn, destroy, install, uninstall
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def spawn_from_config(group_name, config):
|
|
280
|
+
match config.source.mode:
|
|
281
|
+
case "spawn":
|
|
282
|
+
args = {
|
|
283
|
+
"group_name": group_name,
|
|
284
|
+
"count": config.source.count,
|
|
285
|
+
"role": config.role,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
match config.source.spawn.provider:
|
|
289
|
+
case "vagrant":
|
|
290
|
+
args |= {
|
|
291
|
+
"provider": commands.Providers.VAGRANT,
|
|
292
|
+
"size": config.source.spawn.vagrant.memory,
|
|
293
|
+
"platform": config.source.spawn.vagrant.box,
|
|
294
|
+
"vagrant_cpus": config.source.spawn.vagrant.cpus,
|
|
295
|
+
"vagrant_sync_folder": config.source.spawn.vagrant.sync_folder,
|
|
296
|
+
"vagrant_provision": config.source.spawn.vagrant.provision,
|
|
297
|
+
}
|
|
298
|
+
case "aws":
|
|
299
|
+
args |= {
|
|
300
|
+
"provider": commands.Providers.AWS,
|
|
301
|
+
"platform": config.source.spawn.aws.image,
|
|
302
|
+
"size": config.source.spawn.aws.size,
|
|
303
|
+
"region": config.source.spawn.aws.region,
|
|
304
|
+
}
|
|
305
|
+
case "gcp":
|
|
306
|
+
args |= {
|
|
307
|
+
"provider": commands.Providers.GCP,
|
|
308
|
+
"platform": config.source.spawn.gcp.image,
|
|
309
|
+
"network": config.source.spawn.gcp.network,
|
|
310
|
+
"public_ip": config.source.spawn.gcp.public_ip,
|
|
311
|
+
"size": config.source.spawn.gcp.size,
|
|
312
|
+
"region": config.source.spawn.gcp.region,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
commands.spawn(**args)
|
|
316
|
+
case "save":
|
|
317
|
+
commands.save(name=group_name, hosts=config.source.hosts, role=config.role)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def poll_connection(public_ip):
|
|
321
|
+
users = [
|
|
322
|
+
"Administrator",
|
|
323
|
+
"admin",
|
|
324
|
+
"ubuntu",
|
|
325
|
+
"ec2-user",
|
|
326
|
+
"centos",
|
|
327
|
+
"vagrant",
|
|
328
|
+
"root",
|
|
329
|
+
]
|
|
330
|
+
if utils.whoami() not in users:
|
|
331
|
+
users = [utils.whoami()] + users
|
|
332
|
+
|
|
333
|
+
i = 0
|
|
334
|
+
|
|
335
|
+
while True:
|
|
336
|
+
try:
|
|
337
|
+
hosts = [aramid.Host(public_ip[0], user=users[i])]
|
|
338
|
+
aramid.execute(
|
|
339
|
+
hosts, "whoami", echo=False, echo_cmd=False, ignore_failed=False
|
|
340
|
+
)
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
except aramid.AramidError:
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
i = (i + 1) % len(users)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def install_from_config(key, public_ips, configs, private_ips):
|
|
350
|
+
ip = public_ips[key]
|
|
351
|
+
config = configs[key]
|
|
352
|
+
bootstrap_key = config.cfengine.bootstrap
|
|
353
|
+
|
|
354
|
+
if bootstrap_key is not None:
|
|
355
|
+
if bootstrap_key not in private_ips:
|
|
356
|
+
raise UserError(f"Group '{key}' doesn't have private ips")
|
|
357
|
+
bootstrap = private_ips[bootstrap_key]
|
|
358
|
+
else:
|
|
359
|
+
bootstrap = None
|
|
360
|
+
|
|
361
|
+
if not config.cfengine:
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
args = {
|
|
365
|
+
"bootstrap": bootstrap,
|
|
366
|
+
"package": config.cfengine.package,
|
|
367
|
+
"hub_package": config.cfengine.hub_package,
|
|
368
|
+
"client_package": config.cfengine.client_package,
|
|
369
|
+
"version": config.cfengine.version,
|
|
370
|
+
"demo": config.cfengine.demo,
|
|
371
|
+
"call_collect": config.cfengine.call_collect,
|
|
372
|
+
"edition": config.cfengine.edition,
|
|
373
|
+
"remote_download": config.cfengine.remote_download,
|
|
374
|
+
"insecure": config.cfengine.insecure,
|
|
375
|
+
"trust_keys": config.cfengine.trust_keys,
|
|
376
|
+
"hubs": None,
|
|
377
|
+
"clients": None,
|
|
378
|
+
}
|
|
379
|
+
if config.role == "hub":
|
|
380
|
+
args["hubs"] = ip
|
|
381
|
+
else:
|
|
382
|
+
args["clients"] = ip
|
|
383
|
+
|
|
384
|
+
commands.install(**args)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def up_do(old_state, new_state, config):
|
|
388
|
+
|
|
389
|
+
spawn, destroy, install, uninstall = generate_diff(old_state, new_state)
|
|
390
|
+
|
|
391
|
+
data = utils.read_json(paths.CLOUD_STATE_FPATH)
|
|
392
|
+
if data is None:
|
|
393
|
+
data = {}
|
|
394
|
+
|
|
395
|
+
for key in destroy:
|
|
396
|
+
print("Destroying '%s'" % key)
|
|
397
|
+
if f"@{key}" not in data:
|
|
398
|
+
raise UserError("Cannot destroy %s: group doesn't exist" % key)
|
|
399
|
+
commands.destroy(key)
|
|
400
|
+
|
|
401
|
+
for key in spawn:
|
|
402
|
+
print("Spawning '%s'" % key)
|
|
403
|
+
spawn_from_config(key, config[key])
|
|
404
|
+
|
|
405
|
+
data = utils.read_json(paths.CLOUD_STATE_FPATH)
|
|
406
|
+
if data is None:
|
|
407
|
+
data = {}
|
|
408
|
+
|
|
409
|
+
public_ips = {}
|
|
410
|
+
private_ips = {}
|
|
411
|
+
for group, group_info in data.items():
|
|
412
|
+
group = group[1:]
|
|
413
|
+
for host, host_info in group_info.items():
|
|
414
|
+
if host == "meta":
|
|
415
|
+
continue
|
|
416
|
+
|
|
417
|
+
if "public_ips" in host_info:
|
|
418
|
+
public_ips[group] = [host_info["public_ips"][0]]
|
|
419
|
+
|
|
420
|
+
if "private_ips" in host_info:
|
|
421
|
+
private_ips[group] = [host_info["private_ips"][0]]
|
|
422
|
+
|
|
423
|
+
for key in uninstall:
|
|
424
|
+
print("Uninstalling CFEngine on '%s'" % key)
|
|
425
|
+
if f"@{key}" not in data:
|
|
426
|
+
raise UserError("Cannot uninstall %s: group doesn't exist" % key)
|
|
427
|
+
commands.uninstall(public_ips, purge=True)
|
|
428
|
+
|
|
429
|
+
install = sorted(install, key=lambda x: config[x].role)[::-1]
|
|
430
|
+
for key in install:
|
|
431
|
+
# We poll the VM to be sure it is up, otherwise the installation starts and fails
|
|
432
|
+
poll_connection(public_ips[key])
|
|
433
|
+
|
|
434
|
+
print("Installing CFEngine on '%s'" % key)
|
|
435
|
+
install_from_config(key, public_ips, config, private_ips)
|
|
436
|
+
|
|
437
|
+
return True
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
templates:
|
|
2
|
+
cfengine-3.27.0:
|
|
3
|
+
version: 3.27.0
|
|
4
|
+
edition: enterprise
|
|
5
|
+
bootstrap: hub1
|
|
6
|
+
demo: false
|
|
7
|
+
|
|
8
|
+
cfengine-master:
|
|
9
|
+
remote_download: true
|
|
10
|
+
hub_package: "http://buildcache.cfengine.com/packages/testing-pr/jenkins-master-nightly-pipeline-333/PACKAGES_HUB_x86_64_linux_debian_11/cfengine-nova-hub_3.28.0a.1433c0c2f~133.debian11_amd64.deb"
|
|
11
|
+
client_package: "http://buildcache.cfengine.com/packages/testing-pr/jenkins-master-nightly-pipeline-333/PACKAGES_x86_64_linux_debian_11/cfengine-nova_3.28.0a.1433c0c2f~133.debian11_amd64.deb"
|
|
12
|
+
bootstrap: hub2
|
|
13
|
+
|
|
14
|
+
groups:
|
|
15
|
+
hub1:
|
|
16
|
+
role: hub
|
|
17
|
+
cfengine: cfengine-3.27.0
|
|
18
|
+
source:
|
|
19
|
+
mode: spawn
|
|
20
|
+
spawn:
|
|
21
|
+
provider: vagrant
|
|
22
|
+
vagrant:
|
|
23
|
+
box: ubuntu/focal64
|
|
24
|
+
memory: 512
|
|
25
|
+
cpus: 1
|
|
26
|
+
sync_folder: null
|
|
27
|
+
provision: null
|
|
28
|
+
count: 1
|
|
29
|
+
|
|
30
|
+
hub2:
|
|
31
|
+
role: hub
|
|
32
|
+
cfengine: cfengine-master
|
|
33
|
+
source:
|
|
34
|
+
mode: save
|
|
35
|
+
hosts: ["8.8.8.8"]
|
|
36
|
+
|
|
37
|
+
client2:
|
|
38
|
+
role: client
|
|
39
|
+
cfengine: cfengine-3.27.0
|
|
40
|
+
source:
|
|
41
|
+
mode: spawn
|
|
42
|
+
spawn:
|
|
43
|
+
provider: aws
|
|
44
|
+
aws:
|
|
45
|
+
image: debian-13
|
|
46
|
+
size: xlarge
|
|
47
|
+
count: 3
|
|
48
|
+
|
|
49
|
+
client3:
|
|
50
|
+
role: client
|
|
51
|
+
cfengine: cfengine-master
|
|
52
|
+
source:
|
|
53
|
+
mode: spawn
|
|
54
|
+
spawn:
|
|
55
|
+
provider: gcp
|
|
56
|
+
gcp:
|
|
57
|
+
image: debian-13
|
|
58
|
+
network: null
|
|
59
|
+
public_ip: true
|
|
60
|
+
size: n1-standard-1
|
|
61
|
+
count: 2
|
|
62
|
+
|
|
63
|
+
client4:
|
|
64
|
+
role: client
|
|
65
|
+
source:
|
|
66
|
+
mode: spawn
|
|
67
|
+
spawn:
|
|
68
|
+
provider: vagrant
|
|
69
|
+
vagrant:
|
|
70
|
+
box: generic/centos7
|
|
71
|
+
count: 1
|