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.
Files changed (165) hide show
  1. {cfengine-0.16.6 → cfengine-0.16.7}/PKG-INFO +3 -3
  2. {cfengine-0.16.6 → cfengine-0.16.7}/README.md +2 -2
  3. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/PKG-INFO +3 -3
  4. cfengine-0.16.7/src/cfengine.egg-info/scm_version.json +8 -0
  5. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/commands.py +41 -4
  6. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/main.py +3 -0
  7. cfengine-0.16.7/src/cfengine_cli/up.py +437 -0
  8. cfengine-0.16.7/tests/up-validate/001_complex_config.yaml +71 -0
  9. cfengine-0.16.7/tests/up-validate/002_version.x.yaml +16 -0
  10. cfengine-0.16.7/tests/up-validate/003_box.x.yaml +16 -0
  11. cfengine-0.16.7/tests/up-validate/004_count.x.yaml +16 -0
  12. cfengine-0.16.6/src/cfengine.egg-info/scm_version.json +0 -8
  13. cfengine-0.16.6/src/cfengine_cli/up.py +0 -221
  14. cfengine-0.16.6/tests/up-validate/001_complex_config.yaml +0 -71
  15. cfengine-0.16.6/tests/up-validate/002_version.x.yaml +0 -16
  16. cfengine-0.16.6/tests/up-validate/003_box.x.yaml +0 -16
  17. cfengine-0.16.6/tests/up-validate/004_count.x.yaml +0 -16
  18. {cfengine-0.16.6 → cfengine-0.16.7}/.github/dependabot.yml +0 -0
  19. {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/lint-policy-in-other-repos.yml +0 -0
  20. {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/make-check.yml +0 -0
  21. {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/pypi-publish.yml +0 -0
  22. {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/stale.yml +0 -0
  23. {cfengine-0.16.6 → cfengine-0.16.7}/.github/workflows/update-syntax-description.yml +0 -0
  24. {cfengine-0.16.6 → cfengine-0.16.7}/.gitignore +0 -0
  25. {cfengine-0.16.6 → cfengine-0.16.7}/.python-version +0 -0
  26. {cfengine-0.16.6 → cfengine-0.16.7}/CLAUDE.md +0 -0
  27. {cfengine-0.16.6 → cfengine-0.16.7}/HACKING.md +0 -0
  28. {cfengine-0.16.6 → cfengine-0.16.7}/LICENSE +0 -0
  29. {cfengine-0.16.6 → cfengine-0.16.7}/Makefile +0 -0
  30. {cfengine-0.16.6 → cfengine-0.16.7}/ci/01-install.sh +0 -0
  31. {cfengine-0.16.6 → cfengine-0.16.7}/ci/02-safe-tests.sh +0 -0
  32. {cfengine-0.16.6 → cfengine-0.16.7}/ci/03-unsafe-tests.sh +0 -0
  33. {cfengine-0.16.6 → cfengine-0.16.7}/pyproject.toml +0 -0
  34. {cfengine-0.16.6 → cfengine-0.16.7}/setup.cfg +0 -0
  35. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/SOURCES.txt +0 -0
  36. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/dependency_links.txt +0 -0
  37. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/entry_points.txt +0 -0
  38. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/requires.txt +0 -0
  39. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/scm_file_list.json +0 -0
  40. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine.egg-info/top_level.txt +0 -0
  41. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/__init__.py +0 -0
  42. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/__main__.py +0 -0
  43. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/changelog.py +0 -0
  44. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/deptool-README.md +0 -0
  45. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/deptool.py +0 -0
  46. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/dev.py +0 -0
  47. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/docs.py +0 -0
  48. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/format.py +0 -0
  49. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/lint.py +0 -0
  50. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/lint_csv.py +0 -0
  51. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/lint_yml.py +0 -0
  52. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/__init__.py +0 -0
  53. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/analyze.py +0 -0
  54. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/check_download_matches_git.py +0 -0
  55. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/download.py +0 -0
  56. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/generate_git_tags.py +0 -0
  57. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/generate_release_information.py +0 -0
  58. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/generate_vcf_download.py +0 -0
  59. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/masterfiles/generate_vcf_git_checkout.py +0 -0
  60. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/paths.py +0 -0
  61. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/profile.py +0 -0
  62. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/shell.py +0 -0
  63. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/syntax-description.json +0 -0
  64. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/syntax_tree.py +0 -0
  65. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/utils.py +0 -0
  66. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/validate.py +0 -0
  67. {cfengine-0.16.6 → cfengine-0.16.7}/src/cfengine_cli/version.py +0 -0
  68. {cfengine-0.16.6 → cfengine-0.16.7}/tests/README.md +0 -0
  69. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/001_hello_world.expected.cf +0 -0
  70. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/001_hello_world.input.cf +0 -0
  71. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/002_basics.expected.cf +0 -0
  72. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/002_basics.input.cf +0 -0
  73. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/003_wrapping.expected.cf +0 -0
  74. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/003_wrapping.input.cf +0 -0
  75. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/004_comments.expected.cf +0 -0
  76. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/004_comments.input.cf +0 -0
  77. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/005_bundle_comments.expected.cf +0 -0
  78. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/005_bundle_comments.input.cf +0 -0
  79. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/006_remove_empty_comments.expected.cf +0 -0
  80. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/006_remove_empty_comments.input.cf +0 -0
  81. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/007_class_guarded_empty_lines.expected.cf +0 -0
  82. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/007_class_guarded_empty_lines.input.cf +0 -0
  83. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/008_long_string.expected.cf +0 -0
  84. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/008_long_string.input.cf +0 -0
  85. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/009_single_line.expected.cf +0 -0
  86. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/009_single_line.input.cf +0 -0
  87. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/010_stakeholder.expected.cf +0 -0
  88. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/010_stakeholder.input.cf +0 -0
  89. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/011_macros.expected.cf +0 -0
  90. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/011_macros.input.cf +0 -0
  91. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/011_promises.expected.cf +0 -0
  92. {cfengine-0.16.6 → cfengine-0.16.7}/tests/format/011_promises.input.cf +0 -0
  93. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/001_hello_world.cf +0 -0
  94. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/002_deprecations.expected.txt +0 -0
  95. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/002_deprecations.x.cf +0 -0
  96. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/003_name_lowercase.cf +0 -0
  97. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/003_name_lowercase.expected.txt +0 -0
  98. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/003_name_lowercase.x.cf +0 -0
  99. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/004_block_type.cf +0 -0
  100. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/004_block_type.expected.txt +0 -0
  101. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/004_block_type.x.cf +0 -0
  102. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/005_syntax_error.cf +0 -0
  103. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/005_syntax_error.expected.txt +0 -0
  104. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/005_syntax_error.x.cf +0 -0
  105. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/006_empty_file.expected.txt +0 -0
  106. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/006_empty_file.x.cf +0 -0
  107. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/007_namespace.cf +0 -0
  108. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/007_namespace.expected.txt +0 -0
  109. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/007_namespace.x.cf +0 -0
  110. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/008_bundle_shadows_function.cf +0 -0
  111. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/008_bundle_shadows_function.expected.txt +0 -0
  112. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/008_bundle_shadows_function.x.cf +0 -0
  113. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/009_unknown_function_inside_vars.cf +0 -0
  114. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/009_unknown_function_inside_vars.expected.txt +0 -0
  115. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/009_unknown_function_inside_vars.x.cf +0 -0
  116. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/010_mutually_exclusive_types_vars.cf +0 -0
  117. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/010_mutually_exclusive_types_vars.expected.txt +0 -0
  118. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/010_mutually_exclusive_types_vars.x.cf +0 -0
  119. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/011_invalid_attributes.expected.txt +0 -0
  120. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/011_invalid_attributes.x.cf +0 -0
  121. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/012_function_call_arg_count.cf +0 -0
  122. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/012_function_call_arg_count.expected.txt +0 -0
  123. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/012_function_call_arg_count.x.cf +0 -0
  124. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_body.expected.txt +0 -0
  125. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_body.x.cf +0 -0
  126. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_bundle.expected.txt +0 -0
  127. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_bundle.x.cf +0 -0
  128. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/013_num_args_bundle_body.cf +0 -0
  129. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/014_variadic_func_arg_count.cf +0 -0
  130. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/014_variadic_func_arg_count.expected.txt +0 -0
  131. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/014_variadic_func_arg_count.x.cf +0 -0
  132. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/015_macro_multi_def_bundle.cf +0 -0
  133. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/015_macro_multi_def_bundle.expected.txt +0 -0
  134. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/015_macro_multi_def_bundle.x.cf +0 -0
  135. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/016_half_promises.cf +0 -0
  136. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/016_half_promises.expected.txt +0 -0
  137. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/016_half_promises.x.cf +0 -0
  138. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/017_implies_body.cf +0 -0
  139. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/017_implies_body.expected.txt +0 -0
  140. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/017_implies_body.x.cf +0 -0
  141. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/018_nested_calls.cf +0 -0
  142. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/018_nested_calls.expected.txt +0 -0
  143. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/018_nested_calls.x.cf +0 -0
  144. {cfengine-0.16.6 → cfengine-0.16.7}/tests/lint/019_bundle_name_expansion.cf +0 -0
  145. {cfengine-0.16.6 → cfengine-0.16.7}/tests/run-format-tests.sh +0 -0
  146. {cfengine-0.16.6 → cfengine-0.16.7}/tests/run-lint-tests.sh +0 -0
  147. {cfengine-0.16.6 → cfengine-0.16.7}/tests/run-shell-tests.sh +0 -0
  148. {cfengine-0.16.6 → cfengine-0.16.7}/tests/run-up-validate-tests.sh +0 -0
  149. {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/001-help.sh +0 -0
  150. {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/002-version.sh +0 -0
  151. {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/003-format.sh +0 -0
  152. {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/004-format-check.sh +0 -0
  153. {cfengine-0.16.6 → cfengine-0.16.7}/tests/shell/005-lint.sh +0 -0
  154. {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/__init__.py +0 -0
  155. {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_changelog.py +0 -0
  156. {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_deps.py +0 -0
  157. {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_format.py +0 -0
  158. {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_lint_csv.py +0 -0
  159. {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_lint_yml.py +0 -0
  160. {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_paths.py +0 -0
  161. {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_utils.py +0 -0
  162. {cfengine-0.16.6 → cfengine-0.16.7}/tests/unit/test_version.py +0 -0
  163. {cfengine-0.16.6 → cfengine-0.16.7}/tests/up-validate/mocks/cloud_config.json +0 -0
  164. {cfengine-0.16.6 → cfengine-0.16.7}/tests/up-validate/mocks/vagrant +0 -0
  165. {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.6
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.6
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"
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.16.7",
3
+ "distance": 0,
4
+ "node": "g474858503e26c363933858d6151cf3d00924b70e",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-06-25"
8
+ }
@@ -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 a valid yaml config" % args.config)
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
- validate_config(content)
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
- print("Starting VMs...")
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
@@ -0,0 +1,16 @@
1
+ templates:
2
+
3
+ groups:
4
+ hub1:
5
+ role: hub
6
+ cfengine:
7
+ version: 3.27.foo
8
+
9
+ source:
10
+ mode: spawn
11
+ spawn:
12
+ provider: vagrant
13
+ vagrant:
14
+ box: ubuntu/focal64
15
+
16
+ count: 1
@@ -0,0 +1,16 @@
1
+ templates:
2
+
3
+ groups:
4
+ hub1:
5
+ role: hub
6
+ cfengine:
7
+ version: 3.27.0
8
+
9
+ source:
10
+ mode: spawn
11
+ spawn:
12
+ provider: vagrant
13
+ vagrant:
14
+ box: foobar
15
+
16
+ count: 1