cfengine 0.23.1__tar.gz → 0.23.3__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 (183) hide show
  1. {cfengine-0.23.1 → cfengine-0.23.3}/.coveragerc +1 -1
  2. {cfengine-0.23.1 → cfengine-0.23.3}/.github/workflows/lint-policy-in-other-repos.yml +1 -1
  3. {cfengine-0.23.1 → cfengine-0.23.3}/.github/workflows/make-check.yml +1 -1
  4. {cfengine-0.23.1 → cfengine-0.23.3}/.github/workflows/pypi-publish.yml +3 -3
  5. {cfengine-0.23.1 → cfengine-0.23.3}/.github/workflows/renovate.yml +1 -1
  6. {cfengine-0.23.1 → cfengine-0.23.3}/.github/workflows/update-syntax-description.yml +1 -1
  7. {cfengine-0.23.1 → cfengine-0.23.3}/Makefile +3 -1
  8. {cfengine-0.23.1 → cfengine-0.23.3}/PKG-INFO +4 -4
  9. {cfengine-0.23.1 → cfengine-0.23.3}/pyproject.toml +7 -7
  10. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine.egg-info/PKG-INFO +4 -4
  11. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine.egg-info/SOURCES.txt +5 -0
  12. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine.egg-info/requires.txt +3 -3
  13. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine.egg-info/scm_file_list.json +5 -0
  14. cfengine-0.23.3/src/cfengine.egg-info/scm_version.json +8 -0
  15. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/cfengine_wrapper/arg_parse.py +13 -1
  16. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/cfengine_wrapper/cfengine_commands.py +28 -11
  17. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/cfengine_wrapper/cfengine_objects.py +4 -4
  18. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/cfengine_wrapper/cfengine_utils.py +3 -3
  19. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/commands.py +12 -0
  20. cfengine-0.23.3/src/cfengine_cli/container.py +52 -0
  21. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/deptool.py +43 -4
  22. cfengine-0.23.3/src/cfengine_cli/docker/test-agent/Dockerfile +13 -0
  23. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/main.py +45 -6
  24. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/syntax-description.json +26 -14
  25. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/004_block_type.expected.txt +1 -1
  26. {cfengine-0.23.1 → cfengine-0.23.3}/tests/run-shell-tests.sh +6 -0
  27. cfengine-0.23.3/tests/shell/006-test-fail.sh +26 -0
  28. cfengine-0.23.3/tests/shell/007-test-container.sh +14 -0
  29. cfengine-0.23.3/tests/shell/008-cfbs-index.sh +77 -0
  30. {cfengine-0.23.1 → cfengine-0.23.3}/uv.lock +131 -131
  31. cfengine-0.23.1/src/cfengine.egg-info/scm_version.json +0 -8
  32. {cfengine-0.23.1 → cfengine-0.23.3}/.github/renovate-config.js +0 -0
  33. {cfengine-0.23.1 → cfengine-0.23.3}/.github/workflows/coverage.yml +0 -0
  34. {cfengine-0.23.1 → cfengine-0.23.3}/.github/workflows/stale.yml +0 -0
  35. {cfengine-0.23.1 → cfengine-0.23.3}/.gitignore +0 -0
  36. {cfengine-0.23.1 → cfengine-0.23.3}/.python-version +0 -0
  37. {cfengine-0.23.1 → cfengine-0.23.3}/CHEATSHEET.md +0 -0
  38. {cfengine-0.23.1 → cfengine-0.23.3}/CLAUDE.md +0 -0
  39. {cfengine-0.23.1 → cfengine-0.23.3}/HACKING.md +0 -0
  40. {cfengine-0.23.1 → cfengine-0.23.3}/LICENSE +0 -0
  41. {cfengine-0.23.1 → cfengine-0.23.3}/README.md +0 -0
  42. {cfengine-0.23.1 → cfengine-0.23.3}/ci/01-install.sh +0 -0
  43. {cfengine-0.23.1 → cfengine-0.23.3}/ci/02-safe-tests.sh +0 -0
  44. {cfengine-0.23.1 → cfengine-0.23.3}/ci/03-unsafe-tests.sh +0 -0
  45. {cfengine-0.23.1 → cfengine-0.23.3}/renovate.json5 +0 -0
  46. {cfengine-0.23.1 → cfengine-0.23.3}/setup.cfg +0 -0
  47. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine.egg-info/dependency_links.txt +0 -0
  48. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine.egg-info/entry_points.txt +0 -0
  49. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine.egg-info/top_level.txt +0 -0
  50. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/__init__.py +0 -0
  51. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/__main__.py +0 -0
  52. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/cfengine_wrapper/__init__.py +0 -0
  53. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/changelog.py +0 -0
  54. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/deptool-README.md +0 -0
  55. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/dev.py +0 -0
  56. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/docs.py +0 -0
  57. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/format.py +0 -0
  58. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/initialize_project.py +0 -0
  59. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/lint.py +0 -0
  60. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/lint_csv.py +0 -0
  61. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/lint_yml.py +0 -0
  62. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/masterfiles/__init__.py +0 -0
  63. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/masterfiles/analyze.py +0 -0
  64. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/masterfiles/check_download_matches_git.py +0 -0
  65. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/masterfiles/download.py +0 -0
  66. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/masterfiles/generate_git_tags.py +0 -0
  67. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/masterfiles/generate_release_information.py +0 -0
  68. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/masterfiles/generate_vcf_download.py +0 -0
  69. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/masterfiles/generate_vcf_git_checkout.py +0 -0
  70. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/paths.py +0 -0
  71. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/profile.py +0 -0
  72. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/shell.py +0 -0
  73. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/syntax_tree.py +0 -0
  74. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/up.py +0 -0
  75. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/utils.py +0 -0
  76. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/validate.py +0 -0
  77. {cfengine-0.23.1 → cfengine-0.23.3}/src/cfengine_cli/version.py +0 -0
  78. {cfengine-0.23.1 → cfengine-0.23.3}/tests/README.md +0 -0
  79. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/001_hello_world.expected.cf +0 -0
  80. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/001_hello_world.input.cf +0 -0
  81. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/002_basics.expected.cf +0 -0
  82. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/002_basics.input.cf +0 -0
  83. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/003_wrapping.expected.cf +0 -0
  84. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/003_wrapping.input.cf +0 -0
  85. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/004_comments.expected.cf +0 -0
  86. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/004_comments.input.cf +0 -0
  87. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/005_bundle_comments.expected.cf +0 -0
  88. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/005_bundle_comments.input.cf +0 -0
  89. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/006_remove_empty_comments.expected.cf +0 -0
  90. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/006_remove_empty_comments.input.cf +0 -0
  91. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/007_class_guarded_empty_lines.expected.cf +0 -0
  92. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/007_class_guarded_empty_lines.input.cf +0 -0
  93. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/008_long_string.expected.cf +0 -0
  94. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/008_long_string.input.cf +0 -0
  95. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/009_single_line.expected.cf +0 -0
  96. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/009_single_line.input.cf +0 -0
  97. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/010_stakeholder.expected.cf +0 -0
  98. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/010_stakeholder.input.cf +0 -0
  99. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/011_macros.expected.cf +0 -0
  100. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/011_macros.input.cf +0 -0
  101. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/011_promises.expected.cf +0 -0
  102. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/011_promises.input.cf +0 -0
  103. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/012_sameline_comments.expected.cf +0 -0
  104. {cfengine-0.23.1 → cfengine-0.23.3}/tests/format/012_sameline_comments.input.cf +0 -0
  105. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/001_hello_world.cf +0 -0
  106. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/002_deprecations.expected.txt +0 -0
  107. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/002_deprecations.x.cf +0 -0
  108. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/003_name_lowercase.cf +0 -0
  109. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/003_name_lowercase.expected.txt +0 -0
  110. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/003_name_lowercase.x.cf +0 -0
  111. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/004_block_type.cf +0 -0
  112. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/004_block_type.x.cf +0 -0
  113. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/005_syntax_error.cf +0 -0
  114. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/005_syntax_error.expected.txt +0 -0
  115. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/005_syntax_error.x.cf +0 -0
  116. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/006_empty_file.expected.txt +0 -0
  117. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/006_empty_file.x.cf +0 -0
  118. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/007_namespace.cf +0 -0
  119. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/007_namespace.expected.txt +0 -0
  120. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/007_namespace.x.cf +0 -0
  121. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/008_bundle_shadows_function.cf +0 -0
  122. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/008_bundle_shadows_function.expected.txt +0 -0
  123. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/008_bundle_shadows_function.x.cf +0 -0
  124. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/009_unknown_function_inside_vars.cf +0 -0
  125. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/009_unknown_function_inside_vars.expected.txt +0 -0
  126. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/009_unknown_function_inside_vars.x.cf +0 -0
  127. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/010_mutually_exclusive_types_vars.cf +0 -0
  128. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/010_mutually_exclusive_types_vars.expected.txt +0 -0
  129. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/010_mutually_exclusive_types_vars.x.cf +0 -0
  130. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/011_invalid_attributes.expected.txt +0 -0
  131. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/011_invalid_attributes.x.cf +0 -0
  132. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/012_function_call_arg_count.cf +0 -0
  133. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/012_function_call_arg_count.expected.txt +0 -0
  134. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/012_function_call_arg_count.x.cf +0 -0
  135. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/013_num_args_body.expected.txt +0 -0
  136. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/013_num_args_body.x.cf +0 -0
  137. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/013_num_args_bundle.expected.txt +0 -0
  138. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/013_num_args_bundle.x.cf +0 -0
  139. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/013_num_args_bundle_body.cf +0 -0
  140. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/014_variadic_func_arg_count.cf +0 -0
  141. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/014_variadic_func_arg_count.expected.txt +0 -0
  142. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/014_variadic_func_arg_count.x.cf +0 -0
  143. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/015_macro_multi_def_bundle.cf +0 -0
  144. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/015_macro_multi_def_bundle.expected.txt +0 -0
  145. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/015_macro_multi_def_bundle.x.cf +0 -0
  146. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/016_half_promises.cf +0 -0
  147. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/016_half_promises.expected.txt +0 -0
  148. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/016_half_promises.x.cf +0 -0
  149. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/017_implies_body.cf +0 -0
  150. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/017_implies_body.expected.txt +0 -0
  151. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/017_implies_body.x.cf +0 -0
  152. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/018_nested_calls.cf +0 -0
  153. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/018_nested_calls.expected.txt +0 -0
  154. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/018_nested_calls.x.cf +0 -0
  155. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/019_bundle_name_expansion.cf +0 -0
  156. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/020_missing_braces.expected.txt +0 -0
  157. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/020_missing_braces.x.cf +0 -0
  158. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/020_missing_semicolon.expected.txt +0 -0
  159. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/020_missing_semicolon.x.cf +0 -0
  160. {cfengine-0.23.1 → cfengine-0.23.3}/tests/lint/021_reactor_strtotime_silence.cf +0 -0
  161. {cfengine-0.23.1 → cfengine-0.23.3}/tests/run-format-tests.sh +0 -0
  162. {cfengine-0.23.1 → cfengine-0.23.3}/tests/run-lint-tests.sh +0 -0
  163. {cfengine-0.23.1 → cfengine-0.23.3}/tests/run-up-validate-tests.sh +0 -0
  164. {cfengine-0.23.1 → cfengine-0.23.3}/tests/shell/001-help.sh +0 -0
  165. {cfengine-0.23.1 → cfengine-0.23.3}/tests/shell/002-version.sh +0 -0
  166. {cfengine-0.23.1 → cfengine-0.23.3}/tests/shell/003-format.sh +0 -0
  167. {cfengine-0.23.1 → cfengine-0.23.3}/tests/shell/004-format-check.sh +0 -0
  168. {cfengine-0.23.1 → cfengine-0.23.3}/tests/shell/005-lint.sh +0 -0
  169. {cfengine-0.23.1 → cfengine-0.23.3}/tests/unit/__init__.py +0 -0
  170. {cfengine-0.23.1 → cfengine-0.23.3}/tests/unit/test_changelog.py +0 -0
  171. {cfengine-0.23.1 → cfengine-0.23.3}/tests/unit/test_deps.py +0 -0
  172. {cfengine-0.23.1 → cfengine-0.23.3}/tests/unit/test_format.py +0 -0
  173. {cfengine-0.23.1 → cfengine-0.23.3}/tests/unit/test_lint_csv.py +0 -0
  174. {cfengine-0.23.1 → cfengine-0.23.3}/tests/unit/test_lint_yml.py +0 -0
  175. {cfengine-0.23.1 → cfengine-0.23.3}/tests/unit/test_paths.py +0 -0
  176. {cfengine-0.23.1 → cfengine-0.23.3}/tests/unit/test_utils.py +0 -0
  177. {cfengine-0.23.1 → cfengine-0.23.3}/tests/unit/test_version.py +0 -0
  178. {cfengine-0.23.1 → cfengine-0.23.3}/tests/up-validate/001_complex_config.yaml +0 -0
  179. {cfengine-0.23.1 → cfengine-0.23.3}/tests/up-validate/002_version.x.yaml +0 -0
  180. {cfengine-0.23.1 → cfengine-0.23.3}/tests/up-validate/003_box.x.yaml +0 -0
  181. {cfengine-0.23.1 → cfengine-0.23.3}/tests/up-validate/004_count.x.yaml +0 -0
  182. {cfengine-0.23.1 → cfengine-0.23.3}/tests/up-validate/mocks/cloud_config.json +0 -0
  183. {cfengine-0.23.1 → cfengine-0.23.3}/tests/up-validate/mocks/vagrant +0 -0
@@ -1,5 +1,5 @@
1
1
  [run]
2
2
  parallel = True
3
- source = src
3
+ source = ${PROJECT_ROOT}/src
4
4
  concurrency = multiprocessing
5
5
  sigterm = True
@@ -13,7 +13,7 @@ permissions:
13
13
  contents: read
14
14
  jobs:
15
15
  lint:
16
- runs-on: ubuntu-24.04
16
+ runs-on: ubuntu-26.04
17
17
  steps:
18
18
  - name: Checkout cfengine-cli
19
19
  uses: actions/checkout@v7
@@ -19,7 +19,7 @@ permissions:
19
19
  contents: read
20
20
  jobs:
21
21
  check:
22
- runs-on: ubuntu-24.04
22
+ runs-on: ubuntu-26.04
23
23
  strategy:
24
24
  fail-fast: true
25
25
  matrix:
@@ -5,7 +5,7 @@ on:
5
5
  types: [published]
6
6
  jobs:
7
7
  release-build:
8
- runs-on: ubuntu-24.04
8
+ runs-on: ubuntu-26.04
9
9
  permissions:
10
10
  contents: read
11
11
  steps:
@@ -26,7 +26,7 @@ jobs:
26
26
  name: release-dists
27
27
  path: dist/
28
28
  pre-release-tests:
29
- runs-on: ubuntu-24.04
29
+ runs-on: ubuntu-26.04
30
30
  permissions:
31
31
  contents: read
32
32
  steps:
@@ -38,7 +38,7 @@ jobs:
38
38
  uv build
39
39
  uv run pytest
40
40
  pypi-publish:
41
- runs-on: ubuntu-24.04
41
+ runs-on: ubuntu-26.04
42
42
  needs:
43
43
  - release-build
44
44
  - pre-release-tests
@@ -12,7 +12,7 @@ jobs:
12
12
  - name: Checkout
13
13
  uses: actions/checkout@v7.0.1
14
14
  - name: Self-hosted Renovate
15
- uses: renovatebot/github-action@v46.2.4
15
+ uses: renovatebot/github-action@v46.3.7
16
16
  with:
17
17
  configurationFile: .github/renovate-config.js
18
18
  token: ${{ secrets.RENOVATE_TOKEN }}
@@ -15,7 +15,7 @@ jobs:
15
15
  update_syntax_desc:
16
16
  if: contains(fromJSON('["cfengine","mendersoftware","NorthernTechHQ"]'), github.repository_owner)
17
17
  name: Update syntax-description
18
- runs-on: ubuntu-24.04
18
+ runs-on: ubuntu-26.04
19
19
  permissions:
20
20
  contents: write
21
21
  pull-requests: write
@@ -23,7 +23,9 @@ install:
23
23
  pipx install --force --editable .
24
24
 
25
25
 
26
- export COVERAGE_PROCESS_START = $(PWD)/.coveragerc
26
+ coverage: export COVERAGE_PROCESS_START = $(PWD)/.coveragerc
27
+ coverage: export COVERAGE_FILE = $(PWD)/.coverage
28
+ coverage: export PROJECT_ROOT = $(PWD)
27
29
  coverage:
28
30
  uv run coverage erase
29
31
  uv run coverage run --parallel-mode -m pytest
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cfengine
3
- Version: 0.23.1
3
+ Version: 0.23.3
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
@@ -694,13 +694,13 @@ Classifier: Programming Language :: Python :: 3.13
694
694
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
695
695
  Requires-Python: >=3.10
696
696
  Description-Content-Type: text/markdown
697
- Requires-Dist: cf-remote>=0.9.5
698
- Requires-Dist: cfbs>=5.7.0
697
+ Requires-Dist: cf-remote>=0.9.7
698
+ Requires-Dist: cfbs>=5.8.1
699
699
  Requires-Dist: tree-sitter-cfengine>=1.1.12
700
700
  Requires-Dist: tree-sitter>=0.26.0
701
701
  Requires-Dist: markdown-it-py>=4.2.0
702
702
  Requires-Dist: pyyaml>=6.0.3
703
- Requires-Dist: pydantic>=2.13.4
703
+ Requires-Dist: pydantic>=2.13.5
704
704
 
705
705
  # CFEngine command line interface (CLI)
706
706
 
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["setuptools>=84.0.0", "setuptools-scm>=10.2.1"]
2
+ requires = ["setuptools>=84.0.0", "setuptools-scm>=10.3.4"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
@@ -10,13 +10,13 @@ readme = "README.md"
10
10
  license = {file = "LICENSE"}
11
11
  requires-python = ">=3.10"
12
12
  dependencies = [
13
- "cf-remote>=0.9.5",
14
- "cfbs>=5.7.0",
13
+ "cf-remote>=0.9.7",
14
+ "cfbs>=5.8.1",
15
15
  "tree-sitter-cfengine>=1.1.12",
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.4",
19
+ "pydantic>=2.13.5",
20
20
  ]
21
21
  classifiers = [
22
22
  "Development Status :: 3 - Alpha",
@@ -43,7 +43,7 @@ cfengine = "cfengine_cli.main:main"
43
43
  license-files = [] # Workaround bug in setuptools https://github.com/astral-sh/uv/issues/9513
44
44
 
45
45
  [tool.setuptools.package-data]
46
- cfengine_cli = ["*.json"] # syntax-description.json
46
+ cfengine_cli = ["*.json", "docker/test-agent/Dockerfile"] # syntax-description.json, cfengine test's container
47
47
 
48
48
  [tool.pyright]
49
49
  include = ["src"]
@@ -54,7 +54,7 @@ venv = ".venv"
54
54
 
55
55
  [dependency-groups]
56
56
  dev = [
57
- "flake8>=7.3.0",
58
- "pytest>=8.4.2",
57
+ "flake8>=7.4.1",
58
+ "pytest>=9.1.1",
59
59
  "coverage; python_version >= '3.10'",
60
60
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cfengine
3
- Version: 0.23.1
3
+ Version: 0.23.3
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
@@ -694,13 +694,13 @@ Classifier: Programming Language :: Python :: 3.13
694
694
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
695
695
  Requires-Python: >=3.10
696
696
  Description-Content-Type: text/markdown
697
- Requires-Dist: cf-remote>=0.9.5
698
- Requires-Dist: cfbs>=5.7.0
697
+ Requires-Dist: cf-remote>=0.9.7
698
+ Requires-Dist: cfbs>=5.8.1
699
699
  Requires-Dist: tree-sitter-cfengine>=1.1.12
700
700
  Requires-Dist: tree-sitter>=0.26.0
701
701
  Requires-Dist: markdown-it-py>=4.2.0
702
702
  Requires-Dist: pyyaml>=6.0.3
703
- Requires-Dist: pydantic>=2.13.4
703
+ Requires-Dist: pydantic>=2.13.5
704
704
 
705
705
  # CFEngine command line interface (CLI)
706
706
 
@@ -33,6 +33,7 @@ src/cfengine_cli/__init__.py
33
33
  src/cfengine_cli/__main__.py
34
34
  src/cfengine_cli/changelog.py
35
35
  src/cfengine_cli/commands.py
36
+ src/cfengine_cli/container.py
36
37
  src/cfengine_cli/deptool-README.md
37
38
  src/cfengine_cli/deptool.py
38
39
  src/cfengine_cli/dev.py
@@ -57,6 +58,7 @@ src/cfengine_cli/cfengine_wrapper/arg_parse.py
57
58
  src/cfengine_cli/cfengine_wrapper/cfengine_commands.py
58
59
  src/cfengine_cli/cfengine_wrapper/cfengine_objects.py
59
60
  src/cfengine_cli/cfengine_wrapper/cfengine_utils.py
61
+ src/cfengine_cli/docker/test-agent/Dockerfile
60
62
  src/cfengine_cli/masterfiles/__init__.py
61
63
  src/cfengine_cli/masterfiles/analyze.py
62
64
  src/cfengine_cli/masterfiles/check_download_matches_git.py
@@ -158,6 +160,9 @@ tests/shell/002-version.sh
158
160
  tests/shell/003-format.sh
159
161
  tests/shell/004-format-check.sh
160
162
  tests/shell/005-lint.sh
163
+ tests/shell/006-test-fail.sh
164
+ tests/shell/007-test-container.sh
165
+ tests/shell/008-cfbs-index.sh
161
166
  tests/unit/__init__.py
162
167
  tests/unit/test_changelog.py
163
168
  tests/unit/test_deps.py
@@ -1,7 +1,7 @@
1
- cf-remote>=0.9.5
2
- cfbs>=5.7.0
1
+ cf-remote>=0.9.7
2
+ cfbs>=5.8.1
3
3
  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.4
7
+ pydantic>=2.13.5
@@ -31,9 +31,11 @@
31
31
  "src/cfengine_cli/cfengine_wrapper/cfengine_utils.py",
32
32
  "src/cfengine_cli/changelog.py",
33
33
  "src/cfengine_cli/commands.py",
34
+ "src/cfengine_cli/container.py",
34
35
  "src/cfengine_cli/deptool-README.md",
35
36
  "src/cfengine_cli/deptool.py",
36
37
  "src/cfengine_cli/dev.py",
38
+ "src/cfengine_cli/docker/test-agent/Dockerfile",
37
39
  "src/cfengine_cli/docs.py",
38
40
  "src/cfengine_cli/format.py",
39
41
  "src/cfengine_cli/initialize_project.py",
@@ -151,6 +153,9 @@
151
153
  "tests/shell/003-format.sh",
152
154
  "tests/shell/004-format-check.sh",
153
155
  "tests/shell/005-lint.sh",
156
+ "tests/shell/006-test-fail.sh",
157
+ "tests/shell/007-test-container.sh",
158
+ "tests/shell/008-cfbs-index.sh",
154
159
  "tests/unit/__init__.py",
155
160
  "tests/unit/test_changelog.py",
156
161
  "tests/unit/test_deps.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.23.3",
3
+ "distance": 0,
4
+ "node": "gd28211c12d899feeded9307a4a449d762db16550",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-09"
8
+ }
@@ -11,6 +11,15 @@ from cf_remote.args import (
11
11
  )
12
12
 
13
13
 
14
+ def _add_index_arg(parser: argparse.ArgumentParser):
15
+ parser.add_argument(
16
+ "--index",
17
+ help="Specify alternate index (HTTPS URL or relative path to JSON file)",
18
+ type=str,
19
+ default=None,
20
+ )
21
+
22
+
14
23
  def parse_wrapper_args(subp: argparse._SubParsersAction):
15
24
  update_parser = subp.add_parser(
16
25
  "update",
@@ -20,7 +29,7 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):
20
29
  update_parser.add_argument(
21
30
  "to_update",
22
31
  nargs="*",
23
- help="Directory of cfbs-project to update",
32
+ help="Module(s) to update (all modules if omitted)",
24
33
  )
25
34
  remove_parser = subp.add_parser(
26
35
  "remove",
@@ -43,6 +52,7 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):
43
52
  nargs="+",
44
53
  help="Module(s) for which to add",
45
54
  )
55
+ _add_index_arg(add_parser)
46
56
  search_parser = subp.add_parser(
47
57
  "search",
48
58
  help="Searches the build-index for specified module(s)",
@@ -53,6 +63,7 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):
53
63
  nargs="+",
54
64
  help="Module(s) for which to lookup",
55
65
  )
66
+ _add_index_arg(search_parser)
56
67
 
57
68
  input_parser = subp.add_parser(
58
69
  "input",
@@ -84,6 +95,7 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):
84
95
  nargs="*",
85
96
  help="Module(s) for which you would like more info, utilizes cfbs `info` function",
86
97
  )
98
+ _add_index_arg(moduleinfo_parser)
87
99
 
88
100
  show_parser = subp.add_parser(
89
101
  "show", help="Shows your saved host-groups or info about a specified host"
@@ -1,6 +1,8 @@
1
1
  import os
2
2
 
3
+ from cfbs.cfbs_config import CFBSConfig
3
4
  from cfbs.utils import is_cfbs_repo
5
+ from cfbs.validate import validate_index_string
4
6
  from cfbs.commands import (
5
7
  build_command,
6
8
  info_command,
@@ -22,6 +24,7 @@ from cfbs.commands import (
22
24
  )
23
25
 
24
26
  from cfengine_cli.utils import UserError
27
+ from cfengine_cli.container import run_in_container
25
28
  from cfengine_cli.cfengine_wrapper.cfengine_objects import (
26
29
  Executable,
27
30
  ensure_default_agent_flags,
@@ -271,10 +274,7 @@ def deploy(
271
274
 
272
275
  # TODO/WOULD be nice: Deploy without run (CFE-4704: https://northerntech.atlassian.net/browse/CFE-4704)
273
276
  if hubs:
274
- # cf-remote functions use "localhost" (not "local" as it is here)
275
- deploy_targets = [
276
- "localhost" if location == "local" else location for location in hubs
277
- ]
277
+ deploy_targets = [location for location in hubs]
278
278
  error = deploy_command(deploy_targets, masterfiles)
279
279
  else:
280
280
  return deploy_command(hubs, masterfiles)
@@ -287,6 +287,14 @@ def deploy(
287
287
  return error
288
288
 
289
289
 
290
+ def test() -> int:
291
+ rc = build_command()
292
+ if rc != 0:
293
+ return rc
294
+
295
+ return run_in_container("out/masterfiles")
296
+
297
+
290
298
  def show(target: list[str] | None = None) -> int:
291
299
  if target == [] or target is None:
292
300
  return show_command(False)
@@ -295,7 +303,14 @@ def show(target: list[str] | None = None) -> int:
295
303
  return info(target)
296
304
 
297
305
 
298
- def moduleinfo(modules: list[str]) -> int:
306
+ def _init_cfbs_config(index: str | None = None) -> None:
307
+ if index is not None:
308
+ validate_index_string(index)
309
+ CFBSConfig.get_instance(index=index)
310
+
311
+
312
+ def moduleinfo(modules: list[str], index: str | None = None) -> int:
313
+ _init_cfbs_config(index)
299
314
  if modules != []:
300
315
  return info_command(modules)
301
316
  if not is_cfbs_repo():
@@ -312,17 +327,19 @@ def cfbs_input(modules: list[str]) -> int:
312
327
  return input_command(modules, "cfengine input")
313
328
 
314
329
 
315
- def cfbs_add(modules: list[str]) -> int:
316
- return add_command(modules, "cfengine input")
330
+ def cfbs_add(modules: list[str], index: str | None = None) -> int:
331
+ _init_cfbs_config(index)
332
+ return add_command(modules, "cfengine add")
317
333
 
318
334
 
319
- def cfbs_remove(modules: list[str] | None = None) -> int:
320
- return remove_command(modules, "cfengine input")
335
+ def cfbs_remove(modules: list[str]) -> int:
336
+ return remove_command(modules)
321
337
 
322
338
 
323
- def cfbs_update(to_update) -> int:
339
+ def cfbs_update(to_update: list[str]) -> int:
324
340
  return update_command(to_update)
325
341
 
326
342
 
327
- def cfbs_search(modules: list[str]) -> int:
343
+ def cfbs_search(modules: list[str], index: str | None = None) -> int:
344
+ _init_cfbs_config(index)
328
345
  return search_command(modules)
@@ -27,14 +27,14 @@ def ensure_default_agent_flags(command: str) -> str:
27
27
  class Executable:
28
28
  """
29
29
  A single binary (cf-agent or cf-hub) at a known location -- either
30
- "local" or a remote host identifier ("user@ip"). Knows its own path
30
+ localhost or a remote host identifier ("user@ip"). Knows its own path
31
31
  and how to run a command against itself, whether that means a local
32
32
  subprocess or an SSH call via cf-remote.
33
33
  """
34
34
 
35
35
  def __init__(self, name: str, location: str, path: str, aliases=None) -> None:
36
36
  self.name = name # "cf-agent" / "cf-hub"
37
- self.location = location # "local" or "user@ip"
37
+ self.location = location # "localhost" or "user@ip"
38
38
  self.path = path # absolute path to the binary at that location
39
39
  self.aliases = (
40
40
  aliases or []
@@ -42,7 +42,7 @@ class Executable:
42
42
 
43
43
  @property
44
44
  def is_local(self) -> bool:
45
- return self.location == "local"
45
+ return self.location == "localhost"
46
46
 
47
47
  @property
48
48
  def label(self) -> str:
@@ -102,7 +102,7 @@ class Installation:
102
102
 
103
103
  @property
104
104
  def is_local(self) -> bool:
105
- return self.location == "local"
105
+ return self.location == "localhost"
106
106
 
107
107
  @property
108
108
  def aliases(self) -> list:
@@ -111,16 +111,16 @@ _Id = namedtuple("_Id", "location aliases")
111
111
 
112
112
  def _identities(binary_name: str) -> Iterator[_Id]:
113
113
  """local + every known host as (location, aliases), without connecting."""
114
- yield _Id("local", ["localhost"])
114
+ yield _Id("localhost", ["localhost", "local"])
115
115
  for host, aliases in _known_hosts(None if binary_name == "cf-agent" else "hub"):
116
116
  yield _Id(host, aliases)
117
117
 
118
118
 
119
119
  def _resolve(binary_name: str, ident: _Id) -> Executable | None:
120
120
  """Connect (if remote) and build an Executable, or None if unavailable."""
121
- if ident.location == "local":
121
+ if ident.location == "localhost":
122
122
  path = _find_local_path(binary_name)
123
- return Executable(binary_name, "local", path) if path else None
123
+ return Executable(binary_name, "localhost", path) if path else None
124
124
  data = _host_info(ident.location)
125
125
  if not data:
126
126
  return None
@@ -12,6 +12,7 @@ from cfengine_cli.up import validate_config, up_do, resolve_templates
12
12
  from cfengine_cli.initialize_project import init_policy_module, init_promise_type
13
13
  from cf_remote.paths import cf_remote_dir
14
14
  from cfbs.commands import init_command
15
+ from cfengine_cli.cfengine_wrapper.cfengine_commands import test as _cfengine_test
15
16
  from pydantic import ValidationError
16
17
 
17
18
 
@@ -47,6 +48,17 @@ def lint(files, strict, syntax_path) -> int:
47
48
  return errors
48
49
 
49
50
 
51
+ def test(files, strict) -> int:
52
+ errors = _lint(files, strict, None)
53
+ if errors != 0:
54
+ plural = "error" if errors == 1 else "errors"
55
+ print(f"Lint failed, {errors} {plural} in total. Skipping build/deploy/run.")
56
+ return errors
57
+
58
+ print("Lint passed, no errors found.")
59
+ return _cfengine_test()
60
+
61
+
50
62
  def dev(subcommand, args) -> int:
51
63
  return dispatch_dev_subcommand(subcommand, args)
52
64
 
@@ -0,0 +1,52 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+
5
+ from cfengine_cli.utils import UserError
6
+
7
+ _DOCKERFILE_DIR = os.path.join(os.path.dirname(__file__), "docker", "test-agent")
8
+ _IMAGE_TAG = "cfengine-cli-test-agent:latest"
9
+
10
+
11
+ def require_docker() -> None:
12
+ if shutil.which("docker") is None:
13
+ raise UserError(
14
+ "`cfengine test` runs the built policy set inside a Docker container -- install Docker to use it."
15
+ )
16
+
17
+
18
+ def _ensure_image_built() -> None:
19
+ result = subprocess.run(
20
+ ["docker", "build", "-q", "-t", _IMAGE_TAG, _DOCKERFILE_DIR]
21
+ )
22
+ if result.returncode != 0:
23
+ raise UserError("Failed to build the cfengine-test Docker image.")
24
+
25
+
26
+ def run_in_container(masterfiles_dir: str) -> int:
27
+ """
28
+ Runs cf-agent against the given built masterfiles directory inside a container
29
+ """
30
+ require_docker()
31
+ _ensure_image_built()
32
+
33
+ abs_masterfiles = os.path.abspath(masterfiles_dir)
34
+ result = subprocess.run(
35
+ [
36
+ "docker",
37
+ "run",
38
+ "--rm",
39
+ "-v",
40
+ f"{abs_masterfiles}:/mnt/masterfiles:ro",
41
+ _IMAGE_TAG,
42
+ "sh",
43
+ "-c",
44
+ "rm -rf /var/cfengine/inputs "
45
+ "&& rm -rf /var/cfengine/masterfiles "
46
+ "&& cp -r /mnt/masterfiles /var/cfengine/masterfiles "
47
+ "&& /var/cfengine/bin/cf-agent --bootstrap 127.0.0.1 "
48
+ "&& /var/cfengine/bin/cf-agent -KIf update.cf "
49
+ "&& /var/cfengine/bin/cf-agent -KI",
50
+ ]
51
+ )
52
+ return result.returncode
@@ -109,6 +109,8 @@ class GitRepo:
109
109
  a ref from previous work might be left checked out.
110
110
  """
111
111
  self.repo_path = repo_path
112
+ self.repo_owner = repo_owner
113
+ self.repo_name = repo_name
112
114
 
113
115
  repo_url = "https://github.com/{}/{}.git".format(repo_owner, repo_name)
114
116
 
@@ -211,6 +213,36 @@ class GitRepo:
211
213
  return False
212
214
  return True
213
215
 
216
+ def upstream_remote(self):
217
+ result = self.run_command("remote", "-v", capture_output=True)
218
+ upstream = "github\\.com[:/]{}/{}(\\.git)?/?$".format(
219
+ re.escape(self.repo_owner), re.escape(self.repo_name)
220
+ )
221
+ for line in result.stdout.splitlines():
222
+ parts = line.split()
223
+ # GitHub owner and repository names are case-insensitive
224
+ if len(parts) >= 2 and re.search(upstream, parts[1], re.IGNORECASE):
225
+ return parts[0]
226
+ return None
227
+
228
+ def upstream_branch(self, ref):
229
+ remote = self.upstream_remote()
230
+ if remote is None:
231
+ log.warning(
232
+ "No remote points to github.com/{}/{}, using local refs".format(
233
+ self.repo_owner, self.repo_name
234
+ )
235
+ )
236
+ return None
237
+ remote_ref = "{}/{}".format(remote, ref)
238
+ try:
239
+ self.run_command(
240
+ "show-ref", "--verify", "refs/remotes/{}".format(remote_ref)
241
+ )
242
+ except subprocess.CalledProcessError:
243
+ return None
244
+ return remote_ref
245
+
214
246
 
215
247
  def pretty(data):
216
248
  return json.dumps(data, indent=2)
@@ -311,10 +343,17 @@ class DepsReader:
311
343
  ```
312
344
  The ref is checked out during the execution of this function.
313
345
  """
314
- self.buildscripts_repo.checkout(ref)
315
- # also support checking out refs that are not necessarily branches, such as tags
316
- if self.buildscripts_repo.is_git_branch(ref):
317
- self.buildscripts_repo.run_command("pull")
346
+ upstream_ref = self.buildscripts_repo.upstream_branch(ref)
347
+ if upstream_ref is not None:
348
+ self.buildscripts_repo.checkout(upstream_ref)
349
+ else:
350
+ if self.buildscripts_repo.is_git_branch(ref):
351
+ log.warning(
352
+ "Branch '{}' not found in upstream remote, using local branch".format(
353
+ ref
354
+ )
355
+ )
356
+ self.buildscripts_repo.checkout(ref)
318
357
 
319
358
  deps_versions = {}
320
359
  deps_list = self.deps_list(ref)
@@ -0,0 +1,13 @@
1
+ FROM debian:13-slim
2
+ ENV PATH="/root/.local/bin:${PATH}"
3
+
4
+ RUN apt-get update \
5
+ && apt-get install -y --no-install-recommends \
6
+ python3 pipx sudo procps
7
+
8
+ RUN pipx install cfengine
9
+
10
+ RUN cfengine install --clients localhost --edition community
11
+
12
+ RUN mkdir -p /var/cfengine/bin/ \
13
+ && ln -sf "$(command -v python3)" /var/cfengine/bin/cfengine-selected-python