cfengine 0.23.1__tar.gz → 0.23.2__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.2}/.coveragerc +1 -1
  2. {cfengine-0.23.1 → cfengine-0.23.2}/.github/workflows/lint-policy-in-other-repos.yml +1 -1
  3. {cfengine-0.23.1 → cfengine-0.23.2}/.github/workflows/make-check.yml +1 -1
  4. {cfengine-0.23.1 → cfengine-0.23.2}/.github/workflows/pypi-publish.yml +3 -3
  5. {cfengine-0.23.1 → cfengine-0.23.2}/.github/workflows/renovate.yml +1 -1
  6. {cfengine-0.23.1 → cfengine-0.23.2}/.github/workflows/update-syntax-description.yml +1 -1
  7. {cfengine-0.23.1 → cfengine-0.23.2}/Makefile +3 -1
  8. {cfengine-0.23.1 → cfengine-0.23.2}/PKG-INFO +4 -4
  9. {cfengine-0.23.1 → cfengine-0.23.2}/pyproject.toml +7 -7
  10. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine.egg-info/PKG-INFO +4 -4
  11. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine.egg-info/SOURCES.txt +5 -0
  12. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine.egg-info/requires.txt +3 -3
  13. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine.egg-info/scm_file_list.json +5 -0
  14. cfengine-0.23.2/src/cfengine.egg-info/scm_version.json +8 -0
  15. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/cfengine_wrapper/arg_parse.py +13 -1
  16. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/cfengine_wrapper/cfengine_commands.py +28 -11
  17. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/cfengine_wrapper/cfengine_objects.py +4 -4
  18. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/cfengine_wrapper/cfengine_utils.py +3 -3
  19. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/commands.py +12 -0
  20. cfengine-0.23.2/src/cfengine_cli/container.py +52 -0
  21. cfengine-0.23.2/src/cfengine_cli/docker/test-agent/Dockerfile +13 -0
  22. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/main.py +45 -6
  23. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/syntax-description.json +23 -11
  24. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/004_block_type.expected.txt +1 -1
  25. {cfengine-0.23.1 → cfengine-0.23.2}/tests/run-shell-tests.sh +6 -0
  26. cfengine-0.23.2/tests/shell/006-test-fail.sh +26 -0
  27. cfengine-0.23.2/tests/shell/007-test-container.sh +14 -0
  28. cfengine-0.23.2/tests/shell/008-cfbs-index.sh +77 -0
  29. {cfengine-0.23.1 → cfengine-0.23.2}/uv.lock +130 -130
  30. cfengine-0.23.1/src/cfengine.egg-info/scm_version.json +0 -8
  31. {cfengine-0.23.1 → cfengine-0.23.2}/.github/renovate-config.js +0 -0
  32. {cfengine-0.23.1 → cfengine-0.23.2}/.github/workflows/coverage.yml +0 -0
  33. {cfengine-0.23.1 → cfengine-0.23.2}/.github/workflows/stale.yml +0 -0
  34. {cfengine-0.23.1 → cfengine-0.23.2}/.gitignore +0 -0
  35. {cfengine-0.23.1 → cfengine-0.23.2}/.python-version +0 -0
  36. {cfengine-0.23.1 → cfengine-0.23.2}/CHEATSHEET.md +0 -0
  37. {cfengine-0.23.1 → cfengine-0.23.2}/CLAUDE.md +0 -0
  38. {cfengine-0.23.1 → cfengine-0.23.2}/HACKING.md +0 -0
  39. {cfengine-0.23.1 → cfengine-0.23.2}/LICENSE +0 -0
  40. {cfengine-0.23.1 → cfengine-0.23.2}/README.md +0 -0
  41. {cfengine-0.23.1 → cfengine-0.23.2}/ci/01-install.sh +0 -0
  42. {cfengine-0.23.1 → cfengine-0.23.2}/ci/02-safe-tests.sh +0 -0
  43. {cfengine-0.23.1 → cfengine-0.23.2}/ci/03-unsafe-tests.sh +0 -0
  44. {cfengine-0.23.1 → cfengine-0.23.2}/renovate.json5 +0 -0
  45. {cfengine-0.23.1 → cfengine-0.23.2}/setup.cfg +0 -0
  46. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine.egg-info/dependency_links.txt +0 -0
  47. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine.egg-info/entry_points.txt +0 -0
  48. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine.egg-info/top_level.txt +0 -0
  49. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/__init__.py +0 -0
  50. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/__main__.py +0 -0
  51. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/cfengine_wrapper/__init__.py +0 -0
  52. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/changelog.py +0 -0
  53. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/deptool-README.md +0 -0
  54. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/deptool.py +0 -0
  55. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/dev.py +0 -0
  56. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/docs.py +0 -0
  57. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/format.py +0 -0
  58. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/initialize_project.py +0 -0
  59. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/lint.py +0 -0
  60. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/lint_csv.py +0 -0
  61. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/lint_yml.py +0 -0
  62. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/masterfiles/__init__.py +0 -0
  63. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/masterfiles/analyze.py +0 -0
  64. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/masterfiles/check_download_matches_git.py +0 -0
  65. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/masterfiles/download.py +0 -0
  66. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/masterfiles/generate_git_tags.py +0 -0
  67. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/masterfiles/generate_release_information.py +0 -0
  68. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/masterfiles/generate_vcf_download.py +0 -0
  69. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/masterfiles/generate_vcf_git_checkout.py +0 -0
  70. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/paths.py +0 -0
  71. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/profile.py +0 -0
  72. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/shell.py +0 -0
  73. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/syntax_tree.py +0 -0
  74. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/up.py +0 -0
  75. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/utils.py +0 -0
  76. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/validate.py +0 -0
  77. {cfengine-0.23.1 → cfengine-0.23.2}/src/cfengine_cli/version.py +0 -0
  78. {cfengine-0.23.1 → cfengine-0.23.2}/tests/README.md +0 -0
  79. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/001_hello_world.expected.cf +0 -0
  80. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/001_hello_world.input.cf +0 -0
  81. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/002_basics.expected.cf +0 -0
  82. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/002_basics.input.cf +0 -0
  83. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/003_wrapping.expected.cf +0 -0
  84. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/003_wrapping.input.cf +0 -0
  85. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/004_comments.expected.cf +0 -0
  86. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/004_comments.input.cf +0 -0
  87. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/005_bundle_comments.expected.cf +0 -0
  88. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/005_bundle_comments.input.cf +0 -0
  89. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/006_remove_empty_comments.expected.cf +0 -0
  90. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/006_remove_empty_comments.input.cf +0 -0
  91. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/007_class_guarded_empty_lines.expected.cf +0 -0
  92. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/007_class_guarded_empty_lines.input.cf +0 -0
  93. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/008_long_string.expected.cf +0 -0
  94. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/008_long_string.input.cf +0 -0
  95. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/009_single_line.expected.cf +0 -0
  96. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/009_single_line.input.cf +0 -0
  97. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/010_stakeholder.expected.cf +0 -0
  98. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/010_stakeholder.input.cf +0 -0
  99. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/011_macros.expected.cf +0 -0
  100. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/011_macros.input.cf +0 -0
  101. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/011_promises.expected.cf +0 -0
  102. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/011_promises.input.cf +0 -0
  103. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/012_sameline_comments.expected.cf +0 -0
  104. {cfengine-0.23.1 → cfengine-0.23.2}/tests/format/012_sameline_comments.input.cf +0 -0
  105. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/001_hello_world.cf +0 -0
  106. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/002_deprecations.expected.txt +0 -0
  107. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/002_deprecations.x.cf +0 -0
  108. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/003_name_lowercase.cf +0 -0
  109. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/003_name_lowercase.expected.txt +0 -0
  110. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/003_name_lowercase.x.cf +0 -0
  111. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/004_block_type.cf +0 -0
  112. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/004_block_type.x.cf +0 -0
  113. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/005_syntax_error.cf +0 -0
  114. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/005_syntax_error.expected.txt +0 -0
  115. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/005_syntax_error.x.cf +0 -0
  116. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/006_empty_file.expected.txt +0 -0
  117. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/006_empty_file.x.cf +0 -0
  118. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/007_namespace.cf +0 -0
  119. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/007_namespace.expected.txt +0 -0
  120. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/007_namespace.x.cf +0 -0
  121. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/008_bundle_shadows_function.cf +0 -0
  122. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/008_bundle_shadows_function.expected.txt +0 -0
  123. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/008_bundle_shadows_function.x.cf +0 -0
  124. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/009_unknown_function_inside_vars.cf +0 -0
  125. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/009_unknown_function_inside_vars.expected.txt +0 -0
  126. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/009_unknown_function_inside_vars.x.cf +0 -0
  127. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/010_mutually_exclusive_types_vars.cf +0 -0
  128. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/010_mutually_exclusive_types_vars.expected.txt +0 -0
  129. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/010_mutually_exclusive_types_vars.x.cf +0 -0
  130. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/011_invalid_attributes.expected.txt +0 -0
  131. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/011_invalid_attributes.x.cf +0 -0
  132. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/012_function_call_arg_count.cf +0 -0
  133. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/012_function_call_arg_count.expected.txt +0 -0
  134. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/012_function_call_arg_count.x.cf +0 -0
  135. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/013_num_args_body.expected.txt +0 -0
  136. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/013_num_args_body.x.cf +0 -0
  137. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/013_num_args_bundle.expected.txt +0 -0
  138. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/013_num_args_bundle.x.cf +0 -0
  139. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/013_num_args_bundle_body.cf +0 -0
  140. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/014_variadic_func_arg_count.cf +0 -0
  141. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/014_variadic_func_arg_count.expected.txt +0 -0
  142. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/014_variadic_func_arg_count.x.cf +0 -0
  143. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/015_macro_multi_def_bundle.cf +0 -0
  144. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/015_macro_multi_def_bundle.expected.txt +0 -0
  145. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/015_macro_multi_def_bundle.x.cf +0 -0
  146. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/016_half_promises.cf +0 -0
  147. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/016_half_promises.expected.txt +0 -0
  148. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/016_half_promises.x.cf +0 -0
  149. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/017_implies_body.cf +0 -0
  150. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/017_implies_body.expected.txt +0 -0
  151. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/017_implies_body.x.cf +0 -0
  152. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/018_nested_calls.cf +0 -0
  153. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/018_nested_calls.expected.txt +0 -0
  154. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/018_nested_calls.x.cf +0 -0
  155. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/019_bundle_name_expansion.cf +0 -0
  156. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/020_missing_braces.expected.txt +0 -0
  157. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/020_missing_braces.x.cf +0 -0
  158. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/020_missing_semicolon.expected.txt +0 -0
  159. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/020_missing_semicolon.x.cf +0 -0
  160. {cfengine-0.23.1 → cfengine-0.23.2}/tests/lint/021_reactor_strtotime_silence.cf +0 -0
  161. {cfengine-0.23.1 → cfengine-0.23.2}/tests/run-format-tests.sh +0 -0
  162. {cfengine-0.23.1 → cfengine-0.23.2}/tests/run-lint-tests.sh +0 -0
  163. {cfengine-0.23.1 → cfengine-0.23.2}/tests/run-up-validate-tests.sh +0 -0
  164. {cfengine-0.23.1 → cfengine-0.23.2}/tests/shell/001-help.sh +0 -0
  165. {cfengine-0.23.1 → cfengine-0.23.2}/tests/shell/002-version.sh +0 -0
  166. {cfengine-0.23.1 → cfengine-0.23.2}/tests/shell/003-format.sh +0 -0
  167. {cfengine-0.23.1 → cfengine-0.23.2}/tests/shell/004-format-check.sh +0 -0
  168. {cfengine-0.23.1 → cfengine-0.23.2}/tests/shell/005-lint.sh +0 -0
  169. {cfengine-0.23.1 → cfengine-0.23.2}/tests/unit/__init__.py +0 -0
  170. {cfengine-0.23.1 → cfengine-0.23.2}/tests/unit/test_changelog.py +0 -0
  171. {cfengine-0.23.1 → cfengine-0.23.2}/tests/unit/test_deps.py +0 -0
  172. {cfengine-0.23.1 → cfengine-0.23.2}/tests/unit/test_format.py +0 -0
  173. {cfengine-0.23.1 → cfengine-0.23.2}/tests/unit/test_lint_csv.py +0 -0
  174. {cfengine-0.23.1 → cfengine-0.23.2}/tests/unit/test_lint_yml.py +0 -0
  175. {cfengine-0.23.1 → cfengine-0.23.2}/tests/unit/test_paths.py +0 -0
  176. {cfengine-0.23.1 → cfengine-0.23.2}/tests/unit/test_utils.py +0 -0
  177. {cfengine-0.23.1 → cfengine-0.23.2}/tests/unit/test_version.py +0 -0
  178. {cfengine-0.23.1 → cfengine-0.23.2}/tests/up-validate/001_complex_config.yaml +0 -0
  179. {cfengine-0.23.1 → cfengine-0.23.2}/tests/up-validate/002_version.x.yaml +0 -0
  180. {cfengine-0.23.1 → cfengine-0.23.2}/tests/up-validate/003_box.x.yaml +0 -0
  181. {cfengine-0.23.1 → cfengine-0.23.2}/tests/up-validate/004_count.x.yaml +0 -0
  182. {cfengine-0.23.1 → cfengine-0.23.2}/tests/up-validate/mocks/cloud_config.json +0 -0
  183. {cfengine-0.23.1 → cfengine-0.23.2}/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.6
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.2
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.6
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.6",
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.2
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.6
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.6
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.2",
3
+ "distance": 0,
4
+ "node": "g65afc1bd8b3f77fcd47eefe94ee19898b7fce5c8",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-30"
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
@@ -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
@@ -30,7 +30,14 @@ from cf_remote.validate import (
30
30
  validate_deploy_args,
31
31
  validate_destroy_args,
32
32
  )
33
- from cfbs.utils import CFBSProgrammerError
33
+ from cfbs.utils import (
34
+ CFBSProgrammerError,
35
+ CFBSExitError,
36
+ CFBSUserError,
37
+ CFBSNetworkError,
38
+ CFBSValidationError,
39
+ )
40
+ from cfbs.git import CFBSGitError
34
41
 
35
42
 
36
43
  def _get_arg_parser():
@@ -88,6 +95,24 @@ def _get_arg_parser():
88
95
  )
89
96
  lnt.add_argument("files", nargs="*", help="Files to lint")
90
97
 
98
+ tst = subp.add_parser(
99
+ "test",
100
+ help="Lint, then build/run a cfbs project in a throwaway container as one non-interactive check",
101
+ description="A convenience wrapper chaining `cfengine lint`, `cfengine build`, `cfengine deploy` and `cfengine run` "
102
+ "to quickly test a module or policy-set end-to-end.",
103
+ )
104
+ tst.add_argument(
105
+ "--strict",
106
+ type=str,
107
+ default="yes",
108
+ help="Strict mode for linting. Default=yes, checks for undefined promise types, bundles, bodies, functions",
109
+ )
110
+ tst.add_argument(
111
+ "files",
112
+ nargs="*",
113
+ help="Files/Folder to lint (default is . (the entire project) )",
114
+ )
115
+
91
116
  dev_parser = subp.add_parser(
92
117
  "dev", help="Utilities intended for developers / maintainers of CFEngine"
93
118
  )
@@ -230,11 +255,11 @@ def run_command_with_args(args) -> int:
230
255
  if args.command == "input":
231
256
  return cfengine_commands.cfbs_input(args.module)
232
257
  if args.command == "add":
233
- return cfengine_commands.cfbs_add(args.module)
258
+ return cfengine_commands.cfbs_add(args.module, args.index)
234
259
  if args.command == "remove":
235
260
  return cfengine_commands.cfbs_remove(args.module)
236
261
  if args.command == "search":
237
- return cfengine_commands.cfbs_search(args.module)
262
+ return cfengine_commands.cfbs_search(args.module, args.index)
238
263
  if args.command == "update":
239
264
  return cfengine_commands.cfbs_update(args.to_update)
240
265
  if args.command == "format":
@@ -245,6 +270,11 @@ def run_command_with_args(args) -> int:
245
270
  (args.strict.lower() in ("y", "ye", "yes")),
246
271
  args.syntax_description,
247
272
  )
273
+ if args.command == "test":
274
+ return commands.test(
275
+ args.files,
276
+ (args.strict.lower() in ("y", "ye", "yes")),
277
+ )
248
278
  if args.command == "report":
249
279
  return cfengine_commands.report(
250
280
  target=args.hub,
@@ -340,7 +370,7 @@ def run_command_with_args(args) -> int:
340
370
  if args.command == "show":
341
371
  return cfengine_commands.show(args.hosts)
342
372
  if args.command == "moduleinfo":
343
- return cfengine_commands.moduleinfo(args.modules)
373
+ return cfengine_commands.moduleinfo(args.modules, args.index)
344
374
  if args.command == "connect":
345
375
  return cfengine_commands.connect(args.hosts)
346
376
  raise UserError(f"Unknown command: '{args.command}'")
@@ -376,7 +406,7 @@ def validate_args(args):
376
406
  if "hub" in args and args.hub:
377
407
  log.debug(f"validate_args, hubs in args, args.hub='{args.hub}'")
378
408
  if args.hub in ["local", "localhost"]:
379
- args.hub = ["local"]
409
+ args.hub = ["localhost"]
380
410
  else:
381
411
  args.hub = resolve_hosts(args.hub)
382
412
 
@@ -406,7 +436,16 @@ def main():
406
436
  exit_code = _main()
407
437
  assert type(exit_code) is int
408
438
  sys.exit(exit_code)
409
- except (UserError, CFRUserError, CFRExitError) as e:
439
+ except (
440
+ UserError,
441
+ CFRUserError,
442
+ CFRExitError,
443
+ CFBSExitError,
444
+ CFBSUserError,
445
+ CFBSNetworkError,
446
+ CFBSValidationError,
447
+ CFBSGitError,
448
+ ) as e:
410
449
  print(str(e))
411
450
  sys.exit(-1)
412
451
  # Exceptions below are not expected, print extra info: