cfengine 0.18.0__tar.gz → 0.19.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.
- {cfengine-0.18.0 → cfengine-0.19.0}/PKG-INFO +1 -1
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine.egg-info/PKG-INFO +1 -1
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine.egg-info/SOURCES.txt +8 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine.egg-info/scm_file_list.json +8 -0
- cfengine-0.19.0/src/cfengine.egg-info/scm_version.json +8 -0
- cfengine-0.19.0/src/cfengine_cli/cfengine_wrapper/cfengine_commands.py +44 -0
- cfengine-0.19.0/src/cfengine_cli/cfengine_wrapper/cfengine_objects.py +120 -0
- cfengine-0.19.0/src/cfengine_cli/cfengine_wrapper/cfengine_utils.py +191 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/commands.py +0 -39
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/deptool.py +2 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/docs.py +6 -2
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/lint.py +33 -10
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/main.py +166 -7
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/syntax-description.json +121 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/004_block_type.expected.txt +1 -1
- cfengine-0.19.0/tests/lint/020_missing_braces.expected.txt +7 -0
- cfengine-0.19.0/tests/lint/020_missing_braces.x.cf +15 -0
- cfengine-0.19.0/tests/lint/020_missing_semicolon.expected.txt +7 -0
- cfengine-0.19.0/tests/lint/020_missing_semicolon.x.cf +18 -0
- cfengine-0.19.0/tests/unit/__init__.py +0 -0
- cfengine-0.18.0/src/cfengine.egg-info/scm_version.json +0 -8
- {cfengine-0.18.0 → cfengine-0.19.0}/.github/dependabot.yml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/.github/workflows/lint-policy-in-other-repos.yml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/.github/workflows/make-check.yml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/.github/workflows/pypi-publish.yml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/.github/workflows/stale.yml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/.github/workflows/update-syntax-description.yml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/.gitignore +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/.python-version +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/CLAUDE.md +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/HACKING.md +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/LICENSE +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/Makefile +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/README.md +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/ci/01-install.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/ci/02-safe-tests.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/ci/03-unsafe-tests.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/pyproject.toml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/setup.cfg +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine.egg-info/dependency_links.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine.egg-info/entry_points.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine.egg-info/requires.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine.egg-info/top_level.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/__init__.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/__main__.py +0 -0
- {cfengine-0.18.0/src/cfengine_cli/masterfiles → cfengine-0.19.0/src/cfengine_cli/cfengine_wrapper}/__init__.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/changelog.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/deptool-README.md +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/dev.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/format.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/lint_csv.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/lint_yml.py +0 -0
- {cfengine-0.18.0/tests/unit → cfengine-0.19.0/src/cfengine_cli/masterfiles}/__init__.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/masterfiles/analyze.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/masterfiles/check_download_matches_git.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/masterfiles/download.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/masterfiles/generate_git_tags.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/masterfiles/generate_release_information.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/masterfiles/generate_vcf_download.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/masterfiles/generate_vcf_git_checkout.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/paths.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/profile.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/shell.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/syntax_tree.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/up.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/utils.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/validate.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/src/cfengine_cli/version.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/README.md +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/001_hello_world.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/001_hello_world.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/002_basics.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/002_basics.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/003_wrapping.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/003_wrapping.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/004_comments.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/004_comments.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/005_bundle_comments.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/005_bundle_comments.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/006_remove_empty_comments.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/006_remove_empty_comments.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/007_class_guarded_empty_lines.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/007_class_guarded_empty_lines.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/008_long_string.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/008_long_string.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/009_single_line.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/009_single_line.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/010_stakeholder.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/010_stakeholder.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/011_macros.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/011_macros.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/011_promises.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/011_promises.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/012_sameline_comments.expected.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/format/012_sameline_comments.input.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/001_hello_world.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/002_deprecations.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/002_deprecations.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/003_name_lowercase.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/003_name_lowercase.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/003_name_lowercase.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/004_block_type.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/004_block_type.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/005_syntax_error.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/005_syntax_error.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/005_syntax_error.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/006_empty_file.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/006_empty_file.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/007_namespace.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/007_namespace.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/007_namespace.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/008_bundle_shadows_function.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/008_bundle_shadows_function.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/008_bundle_shadows_function.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/009_unknown_function_inside_vars.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/009_unknown_function_inside_vars.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/009_unknown_function_inside_vars.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/010_mutually_exclusive_types_vars.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/010_mutually_exclusive_types_vars.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/010_mutually_exclusive_types_vars.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/011_invalid_attributes.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/011_invalid_attributes.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/012_function_call_arg_count.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/012_function_call_arg_count.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/012_function_call_arg_count.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/013_num_args_body.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/013_num_args_body.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/013_num_args_bundle.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/013_num_args_bundle.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/013_num_args_bundle_body.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/014_variadic_func_arg_count.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/014_variadic_func_arg_count.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/014_variadic_func_arg_count.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/015_macro_multi_def_bundle.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/015_macro_multi_def_bundle.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/015_macro_multi_def_bundle.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/016_half_promises.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/016_half_promises.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/016_half_promises.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/017_implies_body.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/017_implies_body.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/017_implies_body.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/018_nested_calls.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/018_nested_calls.expected.txt +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/018_nested_calls.x.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/lint/019_bundle_name_expansion.cf +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/run-format-tests.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/run-lint-tests.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/run-shell-tests.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/run-up-validate-tests.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/shell/001-help.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/shell/002-version.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/shell/003-format.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/shell/004-format-check.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/shell/005-lint.sh +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/unit/test_changelog.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/unit/test_deps.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/unit/test_format.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/unit/test_lint_csv.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/unit/test_lint_yml.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/unit/test_paths.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/unit/test_utils.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/unit/test_version.py +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/up-validate/001_complex_config.yaml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/up-validate/002_version.x.yaml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/up-validate/003_box.x.yaml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/up-validate/004_count.x.yaml +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/up-validate/mocks/cloud_config.json +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/tests/up-validate/mocks/vagrant +0 -0
- {cfengine-0.18.0 → cfengine-0.19.0}/uv.lock +0 -0
|
@@ -46,6 +46,10 @@ src/cfengine_cli/up.py
|
|
|
46
46
|
src/cfengine_cli/utils.py
|
|
47
47
|
src/cfengine_cli/validate.py
|
|
48
48
|
src/cfengine_cli/version.py
|
|
49
|
+
src/cfengine_cli/cfengine_wrapper/__init__.py
|
|
50
|
+
src/cfengine_cli/cfengine_wrapper/cfengine_commands.py
|
|
51
|
+
src/cfengine_cli/cfengine_wrapper/cfengine_objects.py
|
|
52
|
+
src/cfengine_cli/cfengine_wrapper/cfengine_utils.py
|
|
49
53
|
src/cfengine_cli/masterfiles/__init__.py
|
|
50
54
|
src/cfengine_cli/masterfiles/analyze.py
|
|
51
55
|
src/cfengine_cli/masterfiles/check_download_matches_git.py
|
|
@@ -137,6 +141,10 @@ tests/lint/018_nested_calls.cf
|
|
|
137
141
|
tests/lint/018_nested_calls.expected.txt
|
|
138
142
|
tests/lint/018_nested_calls.x.cf
|
|
139
143
|
tests/lint/019_bundle_name_expansion.cf
|
|
144
|
+
tests/lint/020_missing_braces.expected.txt
|
|
145
|
+
tests/lint/020_missing_braces.x.cf
|
|
146
|
+
tests/lint/020_missing_semicolon.expected.txt
|
|
147
|
+
tests/lint/020_missing_semicolon.x.cf
|
|
140
148
|
tests/shell/001-help.sh
|
|
141
149
|
tests/shell/002-version.sh
|
|
142
150
|
tests/shell/003-format.sh
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
"src/cfengine_cli/__main__.py",
|
|
32
32
|
"src/cfengine_cli/paths.py",
|
|
33
33
|
"src/cfengine_cli/dev.py",
|
|
34
|
+
"src/cfengine_cli/cfengine_wrapper/cfengine_commands.py",
|
|
35
|
+
"src/cfengine_cli/cfengine_wrapper/cfengine_objects.py",
|
|
36
|
+
"src/cfengine_cli/cfengine_wrapper/cfengine_utils.py",
|
|
37
|
+
"src/cfengine_cli/cfengine_wrapper/__init__.py",
|
|
34
38
|
"src/cfengine_cli/masterfiles/check_download_matches_git.py",
|
|
35
39
|
"src/cfengine_cli/masterfiles/generate_vcf_git_checkout.py",
|
|
36
40
|
"src/cfengine_cli/masterfiles/__init__.py",
|
|
@@ -49,6 +53,7 @@
|
|
|
49
53
|
"tests/run-format-tests.sh",
|
|
50
54
|
"tests/lint/015_macro_multi_def_bundle.cf",
|
|
51
55
|
"tests/lint/010_mutually_exclusive_types_vars.cf",
|
|
56
|
+
"tests/lint/020_missing_semicolon.x.cf",
|
|
52
57
|
"tests/lint/010_mutually_exclusive_types_vars.x.cf",
|
|
53
58
|
"tests/lint/013_num_args_body.expected.txt",
|
|
54
59
|
"tests/lint/007_namespace.x.cf",
|
|
@@ -59,7 +64,9 @@
|
|
|
59
64
|
"tests/lint/009_unknown_function_inside_vars.cf",
|
|
60
65
|
"tests/lint/008_bundle_shadows_function.expected.txt",
|
|
61
66
|
"tests/lint/013_num_args_bundle.x.cf",
|
|
67
|
+
"tests/lint/020_missing_semicolon.expected.txt",
|
|
62
68
|
"tests/lint/017_implies_body.cf",
|
|
69
|
+
"tests/lint/020_missing_braces.expected.txt",
|
|
63
70
|
"tests/lint/017_implies_body.expected.txt",
|
|
64
71
|
"tests/lint/008_bundle_shadows_function.cf",
|
|
65
72
|
"tests/lint/003_name_lowercase.x.cf",
|
|
@@ -83,6 +90,7 @@
|
|
|
83
90
|
"tests/lint/011_invalid_attributes.x.cf",
|
|
84
91
|
"tests/lint/018_nested_calls.cf",
|
|
85
92
|
"tests/lint/014_variadic_func_arg_count.cf",
|
|
93
|
+
"tests/lint/020_missing_braces.x.cf",
|
|
86
94
|
"tests/lint/017_implies_body.x.cf",
|
|
87
95
|
"tests/lint/012_function_call_arg_count.x.cf",
|
|
88
96
|
"tests/lint/007_namespace.expected.txt",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from cfbs.commands import build_command
|
|
2
|
+
from cf_remote.commands import deploy as deploy_command
|
|
3
|
+
from cf_remote.commands import install as install_command
|
|
4
|
+
from cf_remote.commands import destroy as destroy_command
|
|
5
|
+
|
|
6
|
+
from cfengine_cli.cfengine_wrapper.cfengine_utils import (
|
|
7
|
+
require_executable,
|
|
8
|
+
require_installation,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def report(target: str | None = None) -> int: # TODO? ENT-14122
|
|
13
|
+
installation = require_installation(target)
|
|
14
|
+
rc = installation.agent.run("-KIf update.cf", "-KI")
|
|
15
|
+
if rc != 0:
|
|
16
|
+
return rc
|
|
17
|
+
return installation.hub.run(
|
|
18
|
+
"--query rebase -H 127.0.0.1", "--query delta -H 127.0.0.1"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run(*args, target: str | None = None) -> int:
|
|
23
|
+
agent = require_executable("cf-agent", target)
|
|
24
|
+
if args:
|
|
25
|
+
return agent.run(*args)
|
|
26
|
+
return agent.run("-KIf update.cf", "-KI")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def install() -> int: # TODO ENT-14117
|
|
30
|
+
return install_command(None, None)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def destroy(groupname, del_all=False) -> int:
|
|
34
|
+
if del_all:
|
|
35
|
+
return destroy_command(None)
|
|
36
|
+
return destroy_command(groupname)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build() -> int: # TODO ENT-14119
|
|
40
|
+
return build_command()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def deploy() -> int: # TODO ENT-14119
|
|
44
|
+
return deploy_command(None, None)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from cf_remote.remote import run_command
|
|
3
|
+
import subprocess
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _ensure_default_agent_flags(command: str) -> str:
|
|
9
|
+
"""
|
|
10
|
+
cf-agent needs -K (no-lock), -I (inform), and -f (specify
|
|
11
|
+
file) to actually run against a policy file the way people expect.
|
|
12
|
+
If `command` has no flags at all -- e.g. someone just ran
|
|
13
|
+
`cfengine run mypolicy.cf` -- prepend "-KIf" so a bare filename works.
|
|
14
|
+
|
|
15
|
+
Deliberately does NOT try to detect and fill in individual missing
|
|
16
|
+
letters (e.g. leaving -f out of "-KI somefile.cf")
|
|
17
|
+
If any flag is present at all, we assume the invocation was deliberate and
|
|
18
|
+
leave it exactly as given.
|
|
19
|
+
"""
|
|
20
|
+
tokens = command.split()
|
|
21
|
+
has_flags = any(t.startswith("-") for t in tokens)
|
|
22
|
+
if has_flags:
|
|
23
|
+
return command
|
|
24
|
+
return f"-KIf {command}".strip()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Executable:
|
|
28
|
+
"""
|
|
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
|
|
31
|
+
and how to run a command against itself, whether that means a local
|
|
32
|
+
subprocess or an SSH call via cf-remote.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, name: str, location: str, path: str, aliases=None) -> None:
|
|
36
|
+
self.name = name # "cf-agent" / "cf-hub"
|
|
37
|
+
self.location = location # "local" or "user@ip"
|
|
38
|
+
self.path = path # absolute path to the binary at that location
|
|
39
|
+
self.aliases = (
|
|
40
|
+
aliases or []
|
|
41
|
+
) # friendly names from cf-remote's saved state, e.g. "hub", "local", "remote"
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def is_local(self) -> bool:
|
|
45
|
+
return self.location == "local"
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def label(self) -> str:
|
|
49
|
+
"""Best human-facing identifier: a friendly alias if we have one, else the location."""
|
|
50
|
+
if self.aliases:
|
|
51
|
+
return f"{self.aliases[0]} ({self.location})"
|
|
52
|
+
return self.location
|
|
53
|
+
|
|
54
|
+
def run(self, *commands) -> int:
|
|
55
|
+
rc = 0
|
|
56
|
+
for command in commands:
|
|
57
|
+
rc = self._run_one(command)
|
|
58
|
+
if rc != 0:
|
|
59
|
+
return rc
|
|
60
|
+
return rc
|
|
61
|
+
|
|
62
|
+
def _run_one(self, command: str) -> int:
|
|
63
|
+
if self.name == "cf-agent":
|
|
64
|
+
command = _ensure_default_agent_flags(command)
|
|
65
|
+
|
|
66
|
+
if self.is_local:
|
|
67
|
+
args = [self.path] + command.split()
|
|
68
|
+
# cf-agent picks its workdir based on privilege: as root it uses
|
|
69
|
+
# /var/cfengine/inputs (where policy actually lives); as a
|
|
70
|
+
# regular user it falls back to ~/.cfagent/inputs instead,
|
|
71
|
+
# which won't have update.cf. Elevate to match the remote path,
|
|
72
|
+
# which already runs everything via sudo.
|
|
73
|
+
if os.geteuid() != 0:
|
|
74
|
+
args = ["sudo"] + args
|
|
75
|
+
result = subprocess.run(args)
|
|
76
|
+
return result.returncode
|
|
77
|
+
|
|
78
|
+
full_command = f"{self.path} {command}"
|
|
79
|
+
logging.warning(f"Executing command {full_command} on {self.location}")
|
|
80
|
+
output = run_command(self.location, full_command, sudo=True)
|
|
81
|
+
if (
|
|
82
|
+
output is None
|
|
83
|
+
): # TODO: Test this, I've had some policy failing but returning output instead or error-code (I think)
|
|
84
|
+
# Error already logged in run_command
|
|
85
|
+
return 1
|
|
86
|
+
if output:
|
|
87
|
+
print(output)
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
def __repr__(self) -> str:
|
|
91
|
+
return f"<Executable {self.name} @ {self.location}>"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class Installation:
|
|
96
|
+
"""
|
|
97
|
+
One coherent system that has BOTH cf-agent and cf-hub, so callers
|
|
98
|
+
that need both (e.g. report()) can pick one location and get a
|
|
99
|
+
matched pair, rather than resolving each binary independently.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
location: str
|
|
103
|
+
agent: Executable
|
|
104
|
+
hub: Executable
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def is_local(self) -> bool:
|
|
108
|
+
return self.location == "local"
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def aliases(self) -> list:
|
|
112
|
+
# agent and hub are always built from the same alias list for a
|
|
113
|
+
# given host (see _find_all_paired), so either would do here.
|
|
114
|
+
return self.agent.aliases
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def label(self) -> str:
|
|
118
|
+
if self.aliases:
|
|
119
|
+
return f"{self.aliases[0]} ({self.location})"
|
|
120
|
+
return self.location
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from cfengine_cli.paths import bin
|
|
8
|
+
from cfengine_cli.utils import UserError
|
|
9
|
+
from cfengine_cli.cfengine_wrapper.cfengine_objects import Executable, Installation
|
|
10
|
+
from cf_remote.remote import get_info
|
|
11
|
+
from cf_remote.paths import CLOUD_STATE_FPATH
|
|
12
|
+
from cf_remote.utils import read_json
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _find_local_path(binary_name: str) -> str | None:
|
|
16
|
+
candidate = bin(binary_name)
|
|
17
|
+
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
|
18
|
+
return candidate
|
|
19
|
+
return shutil.which(binary_name)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _known_hosts(role_filter=None) -> Iterator[tuple[str, list[str]]]:
|
|
23
|
+
"""
|
|
24
|
+
Yields (host_id, aliases) from cf-remote's saved/spawned VM state --
|
|
25
|
+
host_id is "user@ip" (same source _get_hubs() in cf_remote.commands
|
|
26
|
+
reads from); aliases are the friendly names cf-remote knows this host
|
|
27
|
+
by: the group name it was saved/spawned under (e.g. "hub", "hub2")
|
|
28
|
+
and its per-host key within that group. Optionally filter by
|
|
29
|
+
vm["role"] (e.g. "hub" or "client").
|
|
30
|
+
"""
|
|
31
|
+
if not os.path.exists(CLOUD_STATE_FPATH):
|
|
32
|
+
return
|
|
33
|
+
vms_info = read_json(CLOUD_STATE_FPATH)
|
|
34
|
+
if not vms_info:
|
|
35
|
+
return
|
|
36
|
+
for group_name, group in vms_info.items():
|
|
37
|
+
alias_group = group_name.lstrip("@")
|
|
38
|
+
for host_key, vm in group.items():
|
|
39
|
+
if host_key == "meta":
|
|
40
|
+
continue
|
|
41
|
+
if role_filter and vm.get("role") != role_filter:
|
|
42
|
+
continue
|
|
43
|
+
host_id = "{}@{}".format(vm["user"], vm["public_ips"][0])
|
|
44
|
+
aliases = [alias_group]
|
|
45
|
+
if host_key != alias_group:
|
|
46
|
+
aliases.append(host_key)
|
|
47
|
+
yield host_id, aliases
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _find_all(binary_name: str) -> list[Executable]:
|
|
51
|
+
"""Every location -- local, plus every matching remote host -- with `binary_name` installed."""
|
|
52
|
+
executables = []
|
|
53
|
+
|
|
54
|
+
local_path = _find_local_path(binary_name)
|
|
55
|
+
if local_path:
|
|
56
|
+
executables.append(Executable(binary_name, "local", local_path))
|
|
57
|
+
|
|
58
|
+
role_filter = "hub" if binary_name == "cf-hub" else None
|
|
59
|
+
key = "agent" if binary_name == "cf-agent" else "hub"
|
|
60
|
+
for host, aliases in _known_hosts(role_filter=role_filter):
|
|
61
|
+
try:
|
|
62
|
+
data = get_info(host)
|
|
63
|
+
except (Exception, SystemExit) as e:
|
|
64
|
+
"""Need to catch SystemExit as cf-remote's get_info() will SystemExit if
|
|
65
|
+
any ssh-connections does not work, for our case we still want to fetch
|
|
66
|
+
the ones that are up in case the user wants to use a different host"""
|
|
67
|
+
logging.warning(f"Skipping {host}: {e}")
|
|
68
|
+
continue
|
|
69
|
+
if not data:
|
|
70
|
+
continue
|
|
71
|
+
binary_path = data.get(key)
|
|
72
|
+
if binary_path:
|
|
73
|
+
executables.append(
|
|
74
|
+
Executable(binary_name, host, binary_path, aliases=aliases)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return executables
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _find_all_paired() -> list[Installation]:
|
|
81
|
+
"""Every location -- local or remote -- that has BOTH cf-agent and cf-hub."""
|
|
82
|
+
installations = []
|
|
83
|
+
|
|
84
|
+
local_agent_path = _find_local_path("cf-agent")
|
|
85
|
+
local_hub_path = _find_local_path("cf-hub")
|
|
86
|
+
if local_agent_path and local_hub_path:
|
|
87
|
+
installations.append(
|
|
88
|
+
Installation(
|
|
89
|
+
location="local",
|
|
90
|
+
agent=Executable("cf-agent", "local", local_agent_path),
|
|
91
|
+
hub=Executable("cf-hub", "local", local_hub_path),
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
for host, aliases in _known_hosts(role_filter="hub"):
|
|
96
|
+
try:
|
|
97
|
+
data = get_info(host)
|
|
98
|
+
except Exception:
|
|
99
|
+
continue
|
|
100
|
+
if not data:
|
|
101
|
+
continue
|
|
102
|
+
agent_path = data.get("agent")
|
|
103
|
+
hub_path = data.get("hub")
|
|
104
|
+
if agent_path and hub_path:
|
|
105
|
+
installations.append(
|
|
106
|
+
Installation(
|
|
107
|
+
location=host,
|
|
108
|
+
agent=Executable("cf-agent", host, agent_path, aliases=aliases),
|
|
109
|
+
hub=Executable("cf-hub", host, hub_path, aliases=aliases),
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return installations
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _prompt_choice(candidates, description):
|
|
117
|
+
if not sys.stdin.isatty():
|
|
118
|
+
labels = ", ".join(c.label for c in candidates)
|
|
119
|
+
raise UserError(
|
|
120
|
+
f"Multiple installations of {description} found ({labels}) "
|
|
121
|
+
f"and no terminal to prompt on. Specify one with --host."
|
|
122
|
+
)
|
|
123
|
+
print(f"Multiple installations of {description} found:")
|
|
124
|
+
for i, c in enumerate(candidates, 1):
|
|
125
|
+
print(f" {i}) {c.label}")
|
|
126
|
+
while True:
|
|
127
|
+
choice = input(f"Select which to use [1-{len(candidates)}]: ").strip()
|
|
128
|
+
if choice.isdigit() and 1 <= int(choice) <= len(candidates):
|
|
129
|
+
return candidates[int(choice) - 1]
|
|
130
|
+
print("Invalid selection, try again.")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _exact_match(candidate, target: str) -> bool:
|
|
134
|
+
return target == candidate.location or target in candidate.aliases
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _loose_match(candidate, target: str) -> bool:
|
|
138
|
+
"""Substring match, for when nothing matched exactly."""
|
|
139
|
+
if target in candidate.location:
|
|
140
|
+
return True
|
|
141
|
+
return any(target in alias for alias in candidate.aliases)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _select(candidates, description, target: str | None = None):
|
|
145
|
+
"""
|
|
146
|
+
Picks one candidate (an Executable or Installation) out of a list:
|
|
147
|
+
- none found -> UserError
|
|
148
|
+
- `target` given -> match against location OR any alias (exact, or
|
|
149
|
+
substring -- an IP, username, or friendly name like "hub"),
|
|
150
|
+
UserError on no match
|
|
151
|
+
- exactly one -> use it, no prompt
|
|
152
|
+
- multiple, no target -> interactive prompt
|
|
153
|
+
"""
|
|
154
|
+
if not candidates:
|
|
155
|
+
raise UserError(
|
|
156
|
+
f"Could not find {description} locally or on any configured remote host."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if target:
|
|
160
|
+
matches = [c for c in candidates if _exact_match(c, target)]
|
|
161
|
+
if not matches:
|
|
162
|
+
matches = [c for c in candidates if _loose_match(c, target)]
|
|
163
|
+
if not matches:
|
|
164
|
+
available = ", ".join(c.label for c in candidates)
|
|
165
|
+
raise UserError(
|
|
166
|
+
f"No installation of {description} matches '{target}'. Available: {available}"
|
|
167
|
+
)
|
|
168
|
+
if len(matches) == 1:
|
|
169
|
+
return matches[0]
|
|
170
|
+
return _prompt_choice(matches, description)
|
|
171
|
+
|
|
172
|
+
if len(candidates) == 1:
|
|
173
|
+
return candidates[0]
|
|
174
|
+
|
|
175
|
+
return _prompt_choice(candidates, description)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def require_executable(name: str, target: str | None = None) -> Executable:
|
|
179
|
+
chosen = _select(_find_all(name), name, target)
|
|
180
|
+
logging.warning(
|
|
181
|
+
f"Using {'local' if chosen.is_local else 'remote'} installation of {name} ({chosen.label})"
|
|
182
|
+
)
|
|
183
|
+
return chosen
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def require_installation(target: str | None = None) -> Installation:
|
|
187
|
+
chosen = _select(_find_all_paired(), "cf-agent + cf-hub", target)
|
|
188
|
+
logging.warning(
|
|
189
|
+
f"Using {'local' if chosen.is_local else 'remote'} installation of cf-agent and cf-hub ({chosen.label})"
|
|
190
|
+
)
|
|
191
|
+
return chosen
|
|
@@ -5,28 +5,14 @@ import yaml
|
|
|
5
5
|
from cfengine_cli.profile import profile_cfengine, generate_callstack
|
|
6
6
|
from cfengine_cli.dev import dispatch_dev_subcommand
|
|
7
7
|
from cfengine_cli.lint import lint_args
|
|
8
|
-
from cfengine_cli.shell import user_command
|
|
9
|
-
from cfengine_cli.paths import bin
|
|
10
8
|
from cfengine_cli.version import cfengine_cli_version_string
|
|
11
9
|
from cfengine_cli.format import format_paths
|
|
12
10
|
from cfengine_cli.utils import UserError
|
|
13
11
|
from cfengine_cli.up import validate_config, up_do, resolve_templates
|
|
14
|
-
from cfbs.commands import build_command
|
|
15
|
-
from cf_remote.commands import deploy as deploy_command
|
|
16
12
|
from cf_remote.paths import cf_remote_dir
|
|
17
13
|
from pydantic import ValidationError
|
|
18
14
|
|
|
19
15
|
|
|
20
|
-
def _require_cfagent():
|
|
21
|
-
if not os.path.exists(bin("cf-agent")):
|
|
22
|
-
raise UserError(f"cf-agent not found at {bin('cf-agent')}")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _require_cfhub():
|
|
26
|
-
if not os.path.exists(bin("cf-hub")):
|
|
27
|
-
raise UserError(f"cf-hub not found at {bin('cf-hub')}")
|
|
28
|
-
|
|
29
|
-
|
|
30
16
|
def help() -> int:
|
|
31
17
|
print("Example usage:")
|
|
32
18
|
print("cfengine run")
|
|
@@ -38,16 +24,6 @@ def version() -> int:
|
|
|
38
24
|
return 0
|
|
39
25
|
|
|
40
26
|
|
|
41
|
-
def build() -> int:
|
|
42
|
-
r = build_command()
|
|
43
|
-
return r
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def deploy() -> int:
|
|
47
|
-
r = deploy_command(None, None)
|
|
48
|
-
return r
|
|
49
|
-
|
|
50
|
-
|
|
51
27
|
def format(names, line_length, check) -> int:
|
|
52
28
|
return format_paths(names, line_length, check)
|
|
53
29
|
|
|
@@ -69,21 +45,6 @@ def lint(files, strict, syntax_path) -> int:
|
|
|
69
45
|
return errors
|
|
70
46
|
|
|
71
47
|
|
|
72
|
-
def report() -> int:
|
|
73
|
-
_require_cfhub()
|
|
74
|
-
_require_cfagent()
|
|
75
|
-
user_command(f"{bin('cf-agent')} -KIf update.cf && {bin('cf-agent')} -KI")
|
|
76
|
-
user_command(f"{bin('cf-hub')} --query rebase -H 127.0.0.1")
|
|
77
|
-
user_command(f"{bin('cf-hub')} --query delta -H 127.0.0.1")
|
|
78
|
-
return 0
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def run() -> int:
|
|
82
|
-
_require_cfagent()
|
|
83
|
-
user_command(f"{bin('cf-agent')} -KIf update.cf && {bin('cf-agent')} -KI")
|
|
84
|
-
return 0
|
|
85
|
-
|
|
86
|
-
|
|
87
48
|
def dev(subcommand, args) -> int:
|
|
88
49
|
return dispatch_dev_subcommand(subcommand, args)
|
|
89
50
|
|
|
@@ -29,6 +29,7 @@ HUMAN_NAME = {
|
|
|
29
29
|
"zlib": "zlib",
|
|
30
30
|
"librsync": "librsync",
|
|
31
31
|
"leech": "leech",
|
|
32
|
+
"leech2": "leech2",
|
|
32
33
|
"apache": "Apache",
|
|
33
34
|
"apr": "APR",
|
|
34
35
|
"apr-util": "apr-util",
|
|
@@ -60,6 +61,7 @@ HOME_URL = {
|
|
|
60
61
|
"zlib": "https://www.zlib.net/",
|
|
61
62
|
"librsync": "https://github.com/librsync/librsync/releases",
|
|
62
63
|
"leech": "https://github.com/larsewi/leech/releases",
|
|
64
|
+
"leech2": "https://github.com/larsewi/leech2/releases",
|
|
63
65
|
"apache": "https://httpd.apache.org/",
|
|
64
66
|
"apr": "https://apr.apache.org/",
|
|
65
67
|
"apr-util": "https://apr.apache.org/",
|
|
@@ -11,6 +11,7 @@ TODO: This code needs several adjustments to better fit into
|
|
|
11
11
|
import os
|
|
12
12
|
import json
|
|
13
13
|
import subprocess
|
|
14
|
+
import textwrap
|
|
14
15
|
|
|
15
16
|
import markdown_it
|
|
16
17
|
from cfbs.pretty import pretty_file
|
|
@@ -375,8 +376,11 @@ def _run_formatter(tool, args, cwd, install_hint):
|
|
|
375
376
|
check=True,
|
|
376
377
|
cwd=cwd,
|
|
377
378
|
)
|
|
378
|
-
except:
|
|
379
|
-
raise UserError(f"
|
|
379
|
+
except FileNotFoundError:
|
|
380
|
+
raise UserError(f"'{tool}' not found. Install it with: {install_hint}")
|
|
381
|
+
except subprocess.CalledProcessError as e:
|
|
382
|
+
error_msg = textwrap.indent(str(e.stderr).strip(), "> ")
|
|
383
|
+
raise UserError(f"Encountered an error running {tool}:\n{error_msg}")
|
|
380
384
|
return result
|
|
381
385
|
|
|
382
386
|
|
|
@@ -493,14 +493,26 @@ class State:
|
|
|
493
493
|
return
|
|
494
494
|
|
|
495
495
|
|
|
496
|
+
def _last_real_sibling_before(node: Node) -> Node | None:
|
|
497
|
+
"""Walk backwards from node through its preceding siblings, skipping
|
|
498
|
+
comments and macros, to find the last real (content) sibling."""
|
|
499
|
+
sib = node.prev_sibling
|
|
500
|
+
while sib is not None and sib.type in ("comment", "macro"):
|
|
501
|
+
sib = sib.prev_sibling
|
|
502
|
+
return sib
|
|
503
|
+
|
|
504
|
+
|
|
496
505
|
def _check_syntax(policy_file: PolicyFile, state: State) -> int:
|
|
497
|
-
"""Iterate over a syntax tree and print errors if it is empty
|
|
498
|
-
errors.
|
|
506
|
+
"""Iterate over a syntax tree and print errors if it is empty, has syntax
|
|
507
|
+
errors, or is missing expected tokens (e.g. a dropped ';').
|
|
499
508
|
|
|
500
509
|
Notably, printing syntax errors _does not happen during parsing_.
|
|
501
510
|
|
|
502
511
|
Tree sitter has already fully parsed the policy, we iterate over the result
|
|
503
|
-
and see if it has inserted any "ERROR" nodes
|
|
512
|
+
and see if it has inserted any "ERROR" nodes, or any "MISSING" nodes
|
|
513
|
+
(tree-sitter's error recovery can silently insert a synthetic token,
|
|
514
|
+
e.g. a missing ';' right before a closing '}', without ever producing
|
|
515
|
+
an ERROR node.
|
|
504
516
|
|
|
505
517
|
Stops at first error. Returns number of errors, always 0 or 1 in this case.
|
|
506
518
|
"""
|
|
@@ -522,15 +534,26 @@ def _check_syntax(policy_file: PolicyFile, state: State) -> int:
|
|
|
522
534
|
state.start_file(policy_file)
|
|
523
535
|
for node in policy_file.nodes:
|
|
524
536
|
state.navigate(node)
|
|
525
|
-
if node.type != "ERROR":
|
|
526
|
-
continue
|
|
527
537
|
line = node.range.start_point[0] + 1
|
|
528
538
|
column = node.range.start_point[1] + 1
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
539
|
+
if node.type == "ERROR":
|
|
540
|
+
_highlight_range(node, lines)
|
|
541
|
+
location = state.get_location_extended(line, column)
|
|
542
|
+
print(f"Error: Syntax error {location}")
|
|
543
|
+
state.end_file()
|
|
544
|
+
return 1
|
|
545
|
+
if node.is_missing:
|
|
546
|
+
# To avoid pointing at #comment/@macro -lines
|
|
547
|
+
prev = _last_real_sibling_before(node)
|
|
548
|
+
report_node = prev if prev is not None else node
|
|
549
|
+
_highlight_range(report_node, lines)
|
|
550
|
+
line = report_node.range.end_point[0] + 1
|
|
551
|
+
column = report_node.range.end_point[1] + 1
|
|
552
|
+
missing = node.type if not node.is_named else f"token: {node.type}"
|
|
553
|
+
location = state.get_location_extended(line, column)
|
|
554
|
+
print(f"Error: Missing '{missing}' {location}")
|
|
555
|
+
state.end_file()
|
|
556
|
+
return 1
|
|
534
557
|
state.end_file()
|
|
535
558
|
return 0
|
|
536
559
|
|