cfengine 0.23.3__tar.gz → 0.24.0__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 (188) hide show
  1. {cfengine-0.23.3 → cfengine-0.24.0}/.github/workflows/coverage.yml +1 -1
  2. {cfengine-0.23.3 → cfengine-0.24.0}/.github/workflows/lint-policy-in-other-repos.yml +1 -1
  3. {cfengine-0.23.3 → cfengine-0.24.0}/.github/workflows/make-check.yml +1 -1
  4. {cfengine-0.23.3 → cfengine-0.24.0}/.github/workflows/pypi-publish.yml +1 -1
  5. {cfengine-0.23.3 → cfengine-0.24.0}/.github/workflows/renovate.yml +2 -1
  6. {cfengine-0.23.3 → cfengine-0.24.0}/.github/workflows/update-syntax-description.yml +1 -1
  7. cfengine-0.24.0/.python-version +1 -0
  8. {cfengine-0.23.3 → cfengine-0.24.0}/CHEATSHEET.md +2 -2
  9. {cfengine-0.23.3 → cfengine-0.24.0}/CLAUDE.md +1 -1
  10. {cfengine-0.23.3 → cfengine-0.24.0}/HACKING.md +3 -3
  11. {cfengine-0.23.3 → cfengine-0.24.0}/Makefile +1 -1
  12. {cfengine-0.23.3 → cfengine-0.24.0}/PKG-INFO +4 -6
  13. {cfengine-0.23.3 → cfengine-0.24.0}/pyproject.toml +7 -9
  14. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine.egg-info/PKG-INFO +4 -6
  15. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine.egg-info/SOURCES.txt +2 -0
  16. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine.egg-info/requires.txt +1 -1
  17. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine.egg-info/scm_file_list.json +2 -0
  18. cfengine-0.24.0/src/cfengine.egg-info/scm_version.json +8 -0
  19. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/cfengine_wrapper/arg_parse.py +31 -9
  20. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/cfengine_wrapper/cfengine_commands.py +35 -2
  21. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/cfengine_wrapper/cfengine_objects.py +3 -2
  22. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/cfengine_wrapper/cfengine_utils.py +41 -3
  23. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/changelog.py +99 -19
  24. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/commands.py +6 -3
  25. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/deptool-README.md +2 -2
  26. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/deptool.py +10 -18
  27. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/format.py +2 -2
  28. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/initialize_project.py +33 -5
  29. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/lint.py +5 -5
  30. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/main.py +35 -4
  31. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/masterfiles/download.py +2 -2
  32. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/syntax-description.json +81 -1
  33. cfengine-0.24.0/src/cfengine_cli/utils.py +28 -0
  34. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/002_basics.expected.cf +2 -2
  35. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/002_basics.input.cf +2 -2
  36. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/011_promises.expected.cf +1 -1
  37. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/011_promises.input.cf +1 -1
  38. {cfengine-0.23.3 → cfengine-0.24.0}/tests/run-format-tests.sh +1 -1
  39. {cfengine-0.23.3 → cfengine-0.24.0}/tests/run-lint-tests.sh +1 -1
  40. {cfengine-0.23.3 → cfengine-0.24.0}/tests/run-shell-tests.sh +1 -1
  41. {cfengine-0.23.3 → cfengine-0.24.0}/tests/shell/003-format.sh +1 -1
  42. cfengine-0.24.0/tests/unit/test_cfengine_wrapper.py +95 -0
  43. {cfengine-0.23.3 → cfengine-0.24.0}/tests/unit/test_changelog.py +3 -1
  44. cfengine-0.24.0/tests/unit/test_initialize_project.py +11 -0
  45. cfengine-0.24.0/uv.lock +804 -0
  46. cfengine-0.23.3/.python-version +0 -1
  47. cfengine-0.23.3/src/cfengine.egg-info/scm_version.json +0 -8
  48. cfengine-0.23.3/src/cfengine_cli/utils.py +0 -9
  49. cfengine-0.23.3/uv.lock +0 -829
  50. {cfengine-0.23.3 → cfengine-0.24.0}/.coveragerc +0 -0
  51. {cfengine-0.23.3 → cfengine-0.24.0}/.github/renovate-config.js +0 -0
  52. {cfengine-0.23.3 → cfengine-0.24.0}/.github/workflows/stale.yml +0 -0
  53. {cfengine-0.23.3 → cfengine-0.24.0}/.gitignore +0 -0
  54. {cfengine-0.23.3 → cfengine-0.24.0}/LICENSE +0 -0
  55. {cfengine-0.23.3 → cfengine-0.24.0}/README.md +0 -0
  56. {cfengine-0.23.3 → cfengine-0.24.0}/ci/01-install.sh +0 -0
  57. {cfengine-0.23.3 → cfengine-0.24.0}/ci/02-safe-tests.sh +0 -0
  58. {cfengine-0.23.3 → cfengine-0.24.0}/ci/03-unsafe-tests.sh +0 -0
  59. {cfengine-0.23.3 → cfengine-0.24.0}/renovate.json5 +0 -0
  60. {cfengine-0.23.3 → cfengine-0.24.0}/setup.cfg +0 -0
  61. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine.egg-info/dependency_links.txt +0 -0
  62. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine.egg-info/entry_points.txt +0 -0
  63. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine.egg-info/top_level.txt +0 -0
  64. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/__init__.py +0 -0
  65. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/__main__.py +0 -0
  66. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/cfengine_wrapper/__init__.py +0 -0
  67. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/container.py +0 -0
  68. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/dev.py +0 -0
  69. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/docker/test-agent/Dockerfile +0 -0
  70. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/docs.py +0 -0
  71. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/lint_csv.py +0 -0
  72. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/lint_yml.py +0 -0
  73. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/masterfiles/__init__.py +0 -0
  74. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/masterfiles/analyze.py +0 -0
  75. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/masterfiles/check_download_matches_git.py +0 -0
  76. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/masterfiles/generate_git_tags.py +0 -0
  77. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/masterfiles/generate_release_information.py +0 -0
  78. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/masterfiles/generate_vcf_download.py +0 -0
  79. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/masterfiles/generate_vcf_git_checkout.py +0 -0
  80. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/paths.py +0 -0
  81. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/profile.py +0 -0
  82. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/shell.py +0 -0
  83. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/syntax_tree.py +0 -0
  84. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/up.py +0 -0
  85. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/validate.py +0 -0
  86. {cfengine-0.23.3 → cfengine-0.24.0}/src/cfengine_cli/version.py +0 -0
  87. {cfengine-0.23.3 → cfengine-0.24.0}/tests/README.md +0 -0
  88. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/001_hello_world.expected.cf +0 -0
  89. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/001_hello_world.input.cf +0 -0
  90. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/003_wrapping.expected.cf +0 -0
  91. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/003_wrapping.input.cf +0 -0
  92. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/004_comments.expected.cf +0 -0
  93. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/004_comments.input.cf +0 -0
  94. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/005_bundle_comments.expected.cf +0 -0
  95. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/005_bundle_comments.input.cf +0 -0
  96. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/006_remove_empty_comments.expected.cf +0 -0
  97. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/006_remove_empty_comments.input.cf +0 -0
  98. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/007_class_guarded_empty_lines.expected.cf +0 -0
  99. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/007_class_guarded_empty_lines.input.cf +0 -0
  100. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/008_long_string.expected.cf +0 -0
  101. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/008_long_string.input.cf +0 -0
  102. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/009_single_line.expected.cf +0 -0
  103. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/009_single_line.input.cf +0 -0
  104. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/010_stakeholder.expected.cf +0 -0
  105. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/010_stakeholder.input.cf +0 -0
  106. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/011_macros.expected.cf +0 -0
  107. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/011_macros.input.cf +0 -0
  108. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/012_sameline_comments.expected.cf +0 -0
  109. {cfengine-0.23.3 → cfengine-0.24.0}/tests/format/012_sameline_comments.input.cf +0 -0
  110. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/001_hello_world.cf +0 -0
  111. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/002_deprecations.expected.txt +0 -0
  112. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/002_deprecations.x.cf +0 -0
  113. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/003_name_lowercase.cf +0 -0
  114. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/003_name_lowercase.expected.txt +0 -0
  115. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/003_name_lowercase.x.cf +0 -0
  116. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/004_block_type.cf +0 -0
  117. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/004_block_type.expected.txt +0 -0
  118. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/004_block_type.x.cf +0 -0
  119. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/005_syntax_error.cf +0 -0
  120. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/005_syntax_error.expected.txt +0 -0
  121. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/005_syntax_error.x.cf +0 -0
  122. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/006_empty_file.expected.txt +0 -0
  123. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/006_empty_file.x.cf +0 -0
  124. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/007_namespace.cf +0 -0
  125. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/007_namespace.expected.txt +0 -0
  126. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/007_namespace.x.cf +0 -0
  127. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/008_bundle_shadows_function.cf +0 -0
  128. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/008_bundle_shadows_function.expected.txt +0 -0
  129. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/008_bundle_shadows_function.x.cf +0 -0
  130. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/009_unknown_function_inside_vars.cf +0 -0
  131. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/009_unknown_function_inside_vars.expected.txt +0 -0
  132. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/009_unknown_function_inside_vars.x.cf +0 -0
  133. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/010_mutually_exclusive_types_vars.cf +0 -0
  134. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/010_mutually_exclusive_types_vars.expected.txt +0 -0
  135. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/010_mutually_exclusive_types_vars.x.cf +0 -0
  136. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/011_invalid_attributes.expected.txt +0 -0
  137. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/011_invalid_attributes.x.cf +0 -0
  138. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/012_function_call_arg_count.cf +0 -0
  139. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/012_function_call_arg_count.expected.txt +0 -0
  140. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/012_function_call_arg_count.x.cf +0 -0
  141. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/013_num_args_body.expected.txt +0 -0
  142. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/013_num_args_body.x.cf +0 -0
  143. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/013_num_args_bundle.expected.txt +0 -0
  144. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/013_num_args_bundle.x.cf +0 -0
  145. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/013_num_args_bundle_body.cf +0 -0
  146. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/014_variadic_func_arg_count.cf +0 -0
  147. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/014_variadic_func_arg_count.expected.txt +0 -0
  148. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/014_variadic_func_arg_count.x.cf +0 -0
  149. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/015_macro_multi_def_bundle.cf +0 -0
  150. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/015_macro_multi_def_bundle.expected.txt +0 -0
  151. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/015_macro_multi_def_bundle.x.cf +0 -0
  152. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/016_half_promises.cf +0 -0
  153. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/016_half_promises.expected.txt +0 -0
  154. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/016_half_promises.x.cf +0 -0
  155. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/017_implies_body.cf +0 -0
  156. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/017_implies_body.expected.txt +0 -0
  157. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/017_implies_body.x.cf +0 -0
  158. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/018_nested_calls.cf +0 -0
  159. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/018_nested_calls.expected.txt +0 -0
  160. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/018_nested_calls.x.cf +0 -0
  161. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/019_bundle_name_expansion.cf +0 -0
  162. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/020_missing_braces.expected.txt +0 -0
  163. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/020_missing_braces.x.cf +0 -0
  164. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/020_missing_semicolon.expected.txt +0 -0
  165. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/020_missing_semicolon.x.cf +0 -0
  166. {cfengine-0.23.3 → cfengine-0.24.0}/tests/lint/021_reactor_strtotime_silence.cf +0 -0
  167. {cfengine-0.23.3 → cfengine-0.24.0}/tests/run-up-validate-tests.sh +0 -0
  168. {cfengine-0.23.3 → cfengine-0.24.0}/tests/shell/001-help.sh +0 -0
  169. {cfengine-0.23.3 → cfengine-0.24.0}/tests/shell/002-version.sh +0 -0
  170. {cfengine-0.23.3 → cfengine-0.24.0}/tests/shell/004-format-check.sh +0 -0
  171. {cfengine-0.23.3 → cfengine-0.24.0}/tests/shell/005-lint.sh +0 -0
  172. {cfengine-0.23.3 → cfengine-0.24.0}/tests/shell/006-test-fail.sh +0 -0
  173. {cfengine-0.23.3 → cfengine-0.24.0}/tests/shell/007-test-container.sh +0 -0
  174. {cfengine-0.23.3 → cfengine-0.24.0}/tests/shell/008-cfbs-index.sh +0 -0
  175. {cfengine-0.23.3 → cfengine-0.24.0}/tests/unit/__init__.py +0 -0
  176. {cfengine-0.23.3 → cfengine-0.24.0}/tests/unit/test_deps.py +0 -0
  177. {cfengine-0.23.3 → cfengine-0.24.0}/tests/unit/test_format.py +0 -0
  178. {cfengine-0.23.3 → cfengine-0.24.0}/tests/unit/test_lint_csv.py +0 -0
  179. {cfengine-0.23.3 → cfengine-0.24.0}/tests/unit/test_lint_yml.py +0 -0
  180. {cfengine-0.23.3 → cfengine-0.24.0}/tests/unit/test_paths.py +0 -0
  181. {cfengine-0.23.3 → cfengine-0.24.0}/tests/unit/test_utils.py +0 -0
  182. {cfengine-0.23.3 → cfengine-0.24.0}/tests/unit/test_version.py +0 -0
  183. {cfengine-0.23.3 → cfengine-0.24.0}/tests/up-validate/001_complex_config.yaml +0 -0
  184. {cfengine-0.23.3 → cfengine-0.24.0}/tests/up-validate/002_version.x.yaml +0 -0
  185. {cfengine-0.23.3 → cfengine-0.24.0}/tests/up-validate/003_box.x.yaml +0 -0
  186. {cfengine-0.23.3 → cfengine-0.24.0}/tests/up-validate/004_count.x.yaml +0 -0
  187. {cfengine-0.23.3 → cfengine-0.24.0}/tests/up-validate/mocks/cloud_config.json +0 -0
  188. {cfengine-0.23.3 → cfengine-0.24.0}/tests/up-validate/mocks/vagrant +0 -0
@@ -13,7 +13,7 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  name: setup-python
15
15
  env:
16
- PYTHON_VERSION: "3.10"
16
+ PYTHON_VERSION: "3.11"
17
17
  steps:
18
18
  - uses: actions/checkout@v7
19
19
  - uses: actions/setup-python@v7
@@ -37,7 +37,7 @@ jobs:
37
37
  - name: Set up Python
38
38
  uses: actions/setup-python@v7
39
39
  with:
40
- python-version: "3.14"
40
+ python-version: "3.15"
41
41
  - name: Install dependencies
42
42
  working-directory: cfengine-cli
43
43
  run: |
@@ -23,7 +23,7 @@ jobs:
23
23
  strategy:
24
24
  fail-fast: true
25
25
  matrix:
26
- python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
26
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
27
27
  steps:
28
28
  - uses: actions/checkout@v7
29
29
  - name: Set up Python ${{ matrix.python-version }}
@@ -51,4 +51,4 @@ jobs:
51
51
  name: release-dists
52
52
  path: dist/
53
53
  - name: Publish release distributions to PyPI
54
- uses: pypa/gh-action-pypi-publish@v1.14.2
54
+ uses: pypa/gh-action-pypi-publish@v1.14.3
@@ -7,12 +7,13 @@ on:
7
7
 
8
8
  jobs:
9
9
  renovate:
10
+ if: contains(fromJSON('["cfengine","mendersoftware","NorthernTechHQ"]'), github.repository_owner)
10
11
  runs-on: ubuntu-latest
11
12
  steps:
12
13
  - name: Checkout
13
14
  uses: actions/checkout@v7.0.1
14
15
  - name: Self-hosted Renovate
15
- uses: renovatebot/github-action@v46.3.7
16
+ uses: renovatebot/github-action@v46.3.12
16
17
  with:
17
18
  configurationFile: .github/renovate-config.js
18
19
  token: ${{ secrets.RENOVATE_TOKEN }}
@@ -28,7 +28,7 @@ jobs:
28
28
  - name: Set up Python 3.12
29
29
  uses: actions/setup-python@v7
30
30
  with:
31
- python-version: "3.14"
31
+ python-version: "3.15"
32
32
  - name: Install dependencies
33
33
  run: |
34
34
  python -m pip install --upgrade pip
@@ -0,0 +1 @@
1
+ 3.15
@@ -1,6 +1,6 @@
1
1
  # CFEngine CLI cheatsheet
2
2
 
3
- Quick reference for `cfengine COMMANDS`
3
+ Quick reference for `cfengine COMMANDS`.
4
4
  For the full command list, run `cfengine help` or `cfengine COMMAND --help`.
5
5
 
6
6
  ## Managing hosts
@@ -86,7 +86,7 @@ cfengine init --policy-set
86
86
  # Initialize an example build project for working on a custom promise type (python)
87
87
  cfengine init --promise-type
88
88
 
89
- # Initialize an exapmlte module project for build.cfengine.com (or internal use)
89
+ # Initialize an example module project for build.cfengine.com (or internal use)
90
90
  cfengine init --policy-module
91
91
 
92
92
  # Initialize an example policy module that takes input data
@@ -48,4 +48,4 @@ We have different suites:
48
48
  - Unit tests in `tests/unit` test individual python functions.
49
49
  - Formatting tests in `tests/format` test the formatter (`cfengine format`).
50
50
  - Linting tests in `tests/lint` test the linter.
51
- - Shell tests in `tests/shell` tests various subcommands and the tool as a whole in an end-to-end fashion.
51
+ - Shell tests in `tests/shell` test various subcommands and the tool as a whole in an end-to-end fashion.
@@ -16,7 +16,7 @@ If your editor does not do this automatically, you can run these tools from the
16
16
  make format
17
17
  ```
18
18
 
19
- ## Installing from source:
19
+ ## Installing from source
20
20
 
21
21
  For developers working on CFEngine CLI, you can install it globally using pipx:
22
22
 
@@ -53,7 +53,7 @@ uv run bash tests/run-shell-tests.sh
53
53
 
54
54
  ## Releasing new versions
55
55
 
56
- Releases are [automated using a GH Action](https://github.com/cfengine/cfengine-cli/blob/main/.github/workflows/pypi-publish.yml)
56
+ Releases are [automated using a GH Action](https://github.com/cfengine/cfengine-cli/blob/main/.github/workflows/pypi-publish.yml).
57
57
  We create tags and releases through the GitHub UI.
58
58
  Go to the releases section in GitHub:
59
59
 
@@ -107,7 +107,7 @@ Go through the prompts:
107
107
  https://github.com/cfengine/cfengine-cli/releases/tag/0.2.0
108
108
  ```
109
109
 
110
- Then check that everything went well;
110
+ Then check that everything went well:
111
111
 
112
112
  https://github.com/cfengine/cfengine-cli/actions
113
113
 
@@ -7,7 +7,7 @@ venv:
7
7
  uv sync
8
8
 
9
9
  format: venv
10
- uv tool run black . --target-version py310
10
+ uv tool run black . --target-version py311
11
11
  prettier . --write
12
12
  uv run cfengine format ./
13
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cfengine
3
- Version: 0.23.3
3
+ Version: 0.24.0
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
@@ -685,14 +685,12 @@ Classifier: Topic :: Security
685
685
  Classifier: Topic :: Software Development :: Build Tools
686
686
  Classifier: Topic :: System :: Distributed Computing
687
687
  Classifier: Topic :: System :: Systems Administration
688
- Classifier: Programming Language :: Python :: 3
689
- Classifier: Programming Language :: Python :: 3.9
690
- Classifier: Programming Language :: Python :: 3.10
691
688
  Classifier: Programming Language :: Python :: 3.11
692
689
  Classifier: Programming Language :: Python :: 3.12
693
690
  Classifier: Programming Language :: Python :: 3.13
691
+ Classifier: Programming Language :: Python :: 3.14
694
692
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
695
- Requires-Python: >=3.10
693
+ Requires-Python: >=3.11
696
694
  Description-Content-Type: text/markdown
697
695
  Requires-Dist: cf-remote>=0.9.7
698
696
  Requires-Dist: cfbs>=5.8.1
@@ -700,7 +698,7 @@ Requires-Dist: tree-sitter-cfengine>=1.1.12
700
698
  Requires-Dist: tree-sitter>=0.26.0
701
699
  Requires-Dist: markdown-it-py>=4.2.0
702
700
  Requires-Dist: pyyaml>=6.0.3
703
- Requires-Dist: pydantic>=2.13.5
701
+ Requires-Dist: pydantic>=2.14.3
704
702
 
705
703
  # CFEngine command line interface (CLI)
706
704
 
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["setuptools>=84.0.0", "setuptools-scm>=10.3.4"]
2
+ requires = ["setuptools>=84.2.1", "setuptools-scm>=10.3.4"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
@@ -8,7 +8,7 @@ dynamic = ["version"]
8
8
  description = "Human-oriented CLI for interacting with CFEngine policy language, projects, and other CFEngine tools"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
11
- requires-python = ">=3.10"
11
+ requires-python = ">=3.11"
12
12
  dependencies = [
13
13
  "cf-remote>=0.9.7",
14
14
  "cfbs>=5.8.1",
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "tree-sitter>=0.26.0",
17
17
  "markdown-it-py>=4.2.0",
18
18
  "pyyaml>=6.0.3",
19
- "pydantic>=2.13.5",
19
+ "pydantic>=2.14.3",
20
20
  ]
21
21
  classifiers = [
22
22
  "Development Status :: 3 - Alpha",
@@ -27,12 +27,10 @@ classifiers = [
27
27
  "Topic :: Software Development :: Build Tools",
28
28
  "Topic :: System :: Distributed Computing",
29
29
  "Topic :: System :: Systems Administration",
30
- "Programming Language :: Python :: 3",
31
- "Programming Language :: Python :: 3.9",
32
- "Programming Language :: Python :: 3.10",
33
30
  "Programming Language :: Python :: 3.11",
34
31
  "Programming Language :: Python :: 3.12",
35
32
  "Programming Language :: Python :: 3.13",
33
+ "Programming Language :: Python :: 3.14",
36
34
  "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
37
35
  ]
38
36
 
@@ -54,7 +52,7 @@ venv = ".venv"
54
52
 
55
53
  [dependency-groups]
56
54
  dev = [
57
- "flake8>=7.4.1",
58
- "pytest>=9.1.1",
59
- "coverage; python_version >= '3.10'",
55
+ "flake8>=7.5.0",
56
+ "pytest>=9.2.0",
57
+ "coverage",
60
58
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cfengine
3
- Version: 0.23.3
3
+ Version: 0.24.0
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
@@ -685,14 +685,12 @@ Classifier: Topic :: Security
685
685
  Classifier: Topic :: Software Development :: Build Tools
686
686
  Classifier: Topic :: System :: Distributed Computing
687
687
  Classifier: Topic :: System :: Systems Administration
688
- Classifier: Programming Language :: Python :: 3
689
- Classifier: Programming Language :: Python :: 3.9
690
- Classifier: Programming Language :: Python :: 3.10
691
688
  Classifier: Programming Language :: Python :: 3.11
692
689
  Classifier: Programming Language :: Python :: 3.12
693
690
  Classifier: Programming Language :: Python :: 3.13
691
+ Classifier: Programming Language :: Python :: 3.14
694
692
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
695
- Requires-Python: >=3.10
693
+ Requires-Python: >=3.11
696
694
  Description-Content-Type: text/markdown
697
695
  Requires-Dist: cf-remote>=0.9.7
698
696
  Requires-Dist: cfbs>=5.8.1
@@ -700,7 +698,7 @@ Requires-Dist: tree-sitter-cfengine>=1.1.12
700
698
  Requires-Dist: tree-sitter>=0.26.0
701
699
  Requires-Dist: markdown-it-py>=4.2.0
702
700
  Requires-Dist: pyyaml>=6.0.3
703
- Requires-Dist: pydantic>=2.13.5
701
+ Requires-Dist: pydantic>=2.14.3
704
702
 
705
703
  # CFEngine command line interface (CLI)
706
704
 
@@ -164,9 +164,11 @@ tests/shell/006-test-fail.sh
164
164
  tests/shell/007-test-container.sh
165
165
  tests/shell/008-cfbs-index.sh
166
166
  tests/unit/__init__.py
167
+ tests/unit/test_cfengine_wrapper.py
167
168
  tests/unit/test_changelog.py
168
169
  tests/unit/test_deps.py
169
170
  tests/unit/test_format.py
171
+ tests/unit/test_initialize_project.py
170
172
  tests/unit/test_lint_csv.py
171
173
  tests/unit/test_lint_yml.py
172
174
  tests/unit/test_paths.py
@@ -4,4 +4,4 @@ tree-sitter-cfengine>=1.1.12
4
4
  tree-sitter>=0.26.0
5
5
  markdown-it-py>=4.2.0
6
6
  pyyaml>=6.0.3
7
- pydantic>=2.13.5
7
+ pydantic>=2.14.3
@@ -157,9 +157,11 @@
157
157
  "tests/shell/007-test-container.sh",
158
158
  "tests/shell/008-cfbs-index.sh",
159
159
  "tests/unit/__init__.py",
160
+ "tests/unit/test_cfengine_wrapper.py",
160
161
  "tests/unit/test_changelog.py",
161
162
  "tests/unit/test_deps.py",
162
163
  "tests/unit/test_format.py",
164
+ "tests/unit/test_initialize_project.py",
163
165
  "tests/unit/test_lint_csv.py",
164
166
  "tests/unit/test_lint_yml.py",
165
167
  "tests/unit/test_paths.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.24.0",
3
+ "distance": 0,
4
+ "node": "g46f54fca1fcf1a14e6471b1c58c4aa03a75166d7",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-11-06"
8
+ }
@@ -8,6 +8,8 @@ from cf_remote.args import (
8
8
  add_spawn_args,
9
9
  add_destroy_args,
10
10
  add_connect_args,
11
+ add_list_args,
12
+ add_download_args,
11
13
  )
12
14
 
13
15
 
@@ -20,6 +22,15 @@ def _add_index_arg(parser: argparse.ArgumentParser):
20
22
  )
21
23
 
22
24
 
25
+ def _add_version_arg(parser: argparse.ArgumentParser):
26
+ parser.add_argument(
27
+ "--version",
28
+ "-V",
29
+ help="Specify version",
30
+ type=str,
31
+ )
32
+
33
+
23
34
  def parse_wrapper_args(subp: argparse._SubParsersAction):
24
35
  update_parser = subp.add_parser(
25
36
  "update",
@@ -39,7 +50,7 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):
39
50
  remove_parser.add_argument(
40
51
  "module",
41
52
  nargs="+",
42
- help="Module(s) for which to remove",
53
+ help="Module(s) to remove",
43
54
  )
44
55
 
45
56
  add_parser = subp.add_parser(
@@ -50,7 +61,7 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):
50
61
  add_parser.add_argument(
51
62
  "module",
52
63
  nargs="+",
53
- help="Module(s) for which to add",
64
+ help="Module(s) to add",
54
65
  )
55
66
  _add_index_arg(add_parser)
56
67
  search_parser = subp.add_parser(
@@ -61,7 +72,7 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):
61
72
  search_parser.add_argument(
62
73
  "module",
63
74
  nargs="+",
64
- help="Module(s) for which to lookup",
75
+ help="Module(s) to look up",
65
76
  )
66
77
  _add_index_arg(search_parser)
67
78
 
@@ -153,14 +164,25 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):
153
164
  help="Install CFEngine on the given hosts",
154
165
  description="A wrapper around the cf-remote `install` function",
155
166
  )
156
- install_parser.add_argument(
157
- "--version",
158
- "-V",
159
- help="Specify version",
160
- type=str,
161
- )
167
+ _add_version_arg(install_parser)
162
168
  add_install_args(install_parser)
163
169
 
170
+ list_parser = subp.add_parser(
171
+ "list",
172
+ help="List CFEngine packages available for download",
173
+ description="A wrapper around the cf-remote `list` function",
174
+ )
175
+ _add_version_arg(list_parser)
176
+ add_list_args(list_parser)
177
+
178
+ download_parser = subp.add_parser(
179
+ "download",
180
+ help="Download CFEngine packages",
181
+ description="A wrapper around the cf-remote `download` function",
182
+ )
183
+ _add_version_arg(download_parser)
184
+ add_download_args(download_parser)
185
+
164
186
  uninstall_parser = subp.add_parser(
165
187
  "uninstall",
166
188
  help="Uninstall CFEngine on the given hosts",
@@ -31,6 +31,7 @@ from cfengine_cli.cfengine_wrapper.cfengine_objects import (
31
31
  )
32
32
  from cfengine_cli.cfengine_wrapper.cfengine_utils import (
33
33
  extract_agent_file,
34
+ has_known_hubs,
34
35
  prompt_two_options,
35
36
  prompt_yes_no,
36
37
  require_executable,
@@ -197,7 +198,7 @@ def report(
197
198
  for agent in clients:
198
199
  rc = _refresh_agent(agent)
199
200
  if rc != 0:
200
- log.error(f"Refresh failed on {agent.label})")
201
+ log.error(f"Refresh failed on {agent.label}")
201
202
  errors += 1
202
203
 
203
204
  for hub in hubs:
@@ -206,7 +207,7 @@ def report(
206
207
  client_ips = [client.location.split("@", 1)[1] for client in clients]
207
208
  rc = _query_hub_delta(hub.hub, client_ips)
208
209
  if rc != 0:
209
- log.error(f"Hub refresh failed on {hub.label})")
210
+ log.error(f"Hub refresh failed on {hub.label}")
210
211
  errors += 1
211
212
 
212
213
  if errors > 0:
@@ -219,9 +220,37 @@ def setup_code(target: str | None = None) -> int:
219
220
  return hub.run("--new-setup-code")
220
221
 
221
222
 
223
+ def _policy_changed_since_build() -> bool:
224
+ """True if there is no build, or project files changed since the last one."""
225
+ tarball = os.path.join("out", "masterfiles.tgz")
226
+ if not os.path.isfile(tarball):
227
+ return True
228
+ built_at = os.path.getmtime(tarball)
229
+ for root, dirs, files in os.walk("."):
230
+ # Skip build output and hidden entries (.git, editor state, ...)
231
+ dirs[:] = [d for d in dirs if d[0] != "." and (root, d) != (".", "out")]
232
+ for name in files:
233
+ path = os.path.join(root, name)
234
+ if name[0] != "." and os.path.getmtime(path) > built_at:
235
+ return True
236
+ return False
237
+
238
+
222
239
  def run(*args, target: str | None = None) -> int:
223
240
  agent = require_executable("cf-agent", target)
224
241
  if not args:
242
+ if (
243
+ is_cfbs_repo()
244
+ and has_known_hubs()
245
+ and _policy_changed_since_build()
246
+ and prompt_yes_no(
247
+ "The policy set has changed since it was last built. "
248
+ "Build and deploy it before running?"
249
+ )
250
+ ):
251
+ rc = build_command() or deploy_command(None, None)
252
+ if rc != 0:
253
+ return rc
225
254
  return agent.run("-KIf update.cf", "-KI")
226
255
 
227
256
  resolved = []
@@ -249,6 +278,10 @@ def build(hub=None, non_interactive=False) -> int:
249
278
  rc = build_command()
250
279
  if rc != 0:
251
280
  return rc
281
+ if not hub and not has_known_hubs():
282
+ # Nothing to deploy to, so don't ask. Mirrors the check cf-remote's
283
+ # deploy does (it errors with "No hub to deploy to" in this case).
284
+ return 0
252
285
  if prompt_yes_no(
253
286
  "Deploy the built policy set now?",
254
287
  default=True,
@@ -72,12 +72,13 @@ class Executable:
72
72
  result = subprocess.run(args)
73
73
  return result.returncode
74
74
 
75
- full_command = f"{self.path} {command}"
75
+ # Include stderr, so cf-promises errors (e.g. duplicate bundle) are shown
76
+ full_command = f"{self.path} {command} 2>&1"
76
77
  log.info(f"Executing command {full_command} on {self.location}")
77
78
  output = run_command(self.location, full_command, sudo=True)
78
79
  if (
79
80
  output is None
80
- ): # TODO: Test this, I've had some policy failing but returning output instead or error-code (I think)
81
+ ): # TODO: Test this, I've had some policy failing but returning output instead of error-code (I think)
81
82
  # Error already logged in run_command
82
83
  return 1
83
84
  if output:
@@ -90,6 +90,11 @@ def _known_hosts(role_filter=None) -> Iterator[tuple[str, list[str]]]:
90
90
  yield host_id, aliases
91
91
 
92
92
 
93
+ def has_known_hubs() -> bool:
94
+ """True if cf-remote knows about any saved/spawned hub (without connecting)."""
95
+ return any(True for _ in _known_hosts("hub"))
96
+
97
+
93
98
  @lru_cache(maxsize=None)
94
99
  def _host_info(host: str):
95
100
  try:
@@ -106,7 +111,17 @@ def _hosts_with_info(role_filter=None):
106
111
  yield host, aliases, data
107
112
 
108
113
 
109
- _Id = namedtuple("_Id", "location aliases")
114
+ class _Id(namedtuple("_Id", "location aliases")):
115
+ @property
116
+ def label(self) -> str:
117
+ if self.aliases and self.location != "localhost":
118
+ return f"{self.aliases[0]} ({self.location})"
119
+ return self.location
120
+
121
+
122
+ def known_hub_locations() -> list[str]:
123
+ """ "user@ip" of every hub cf-remote knows about (without connecting)."""
124
+ return [host for host, _ in _known_hosts("hub")]
110
125
 
111
126
 
112
127
  def _identities(binary_name: str) -> Iterator[_Id]:
@@ -148,11 +163,13 @@ def _find_all_paired() -> list[Installation]:
148
163
 
149
164
 
150
165
  def _prompt_choice(candidates, description):
166
+ """Numbered prompt; pressing Enter picks the first candidate."""
151
167
  print(f"Multiple installations of {description} found:")
152
168
  for i, c in enumerate(candidates, 1):
153
169
  print(f" {i}) {c.label}")
154
170
  while True:
155
- choice = input(f"Select which to use [1-{len(candidates)}]: ").strip()
171
+ choice = input(f"Select which to use [1-{len(candidates)}, default 1]: ")
172
+ choice = choice.strip() or "1"
156
173
  if choice.isdigit() and 1 <= int(choice) <= len(candidates):
157
174
  return candidates[int(choice) - 1]
158
175
  print("Invalid selection, try again.")
@@ -211,6 +228,27 @@ def _select(candidates, description, target: str | None = None):
211
228
  return _prompt_choice(candidates, description)
212
229
 
213
230
 
231
+ def _select_without_connecting(name: str) -> Executable:
232
+ """Only connects to the chosen host. Hubs first, so Enter picks a hub."""
233
+ hubs = set(known_hub_locations())
234
+ remote = [i for i in _identities(name) if i.location != "localhost"]
235
+ local = [_Id("localhost", ["localhost", "local"])] if _find_local_path(name) else []
236
+ ordered = (
237
+ [i for i in remote if i.location in hubs]
238
+ + local
239
+ + [i for i in remote if i.location not in hubs]
240
+ )
241
+ if not ordered:
242
+ raise UserError(
243
+ f"Could not find {name} locally or on any configured remote host."
244
+ )
245
+ ident = ordered[0] if len(ordered) == 1 else _prompt_choice(ordered, name)
246
+ chosen = _resolve(name, ident)
247
+ if chosen is None:
248
+ raise UserError(f"{ident.label} is unreachable or lacks {name}.")
249
+ return chosen
250
+
251
+
214
252
  def require_executable(name: str, target: str | None = None) -> Executable:
215
253
  if isinstance(target, list):
216
254
  if len(target) > 1:
@@ -239,7 +277,7 @@ def require_executable(name: str, target: str | None = None) -> Executable:
239
277
  candidates[0] if len(candidates) == 1 else _prompt_choice(candidates, name)
240
278
  )
241
279
  else:
242
- chosen = _select(_find_all(name), name)
280
+ chosen = _select_without_connecting(name)
243
281
 
244
282
  log.info(
245
283
  f"Using {'local' if chosen.is_local else 'remote'} installation of {name} ({chosen.label})"