xsoar-cli 2.0.0__tar.gz → 2.0.1__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.
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/CHANGELOG.md +6 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/PKG-INFO +1 -1
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/_version.py +2 -2
- xsoar_cli-2.0.1/fixes-todo.md +591 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/cli.py +4 -3
- xsoar_cli-2.0.1/src/xsoar_cli/commands/content/README.md +70 -0
- xsoar_cli-2.0.1/src/xsoar_cli/commands/content/commands.py +172 -0
- xsoar_cli-2.0.1/src/xsoar_cli/utilities/download_content_handlers.py +135 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/content.py +74 -6
- xsoar_cli-2.0.1/tests/test_content.py +699 -0
- xsoar_cli-2.0.0/src/xsoar_cli/commands/content/README.md +0 -39
- xsoar_cli-2.0.0/src/xsoar_cli/commands/content/commands.py +0 -80
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/.github/workflows/main.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/.github/workflows/pull-request-open.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/.github/workflows/release.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/.github/workflows/update-changelog.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/.gitignore +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/CLAUDE.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/CONTRIBUTING.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/LICENSE.txt +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/pyproject.toml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/case/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/case/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/case/commands.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/config/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/config/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/config/commands.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/content/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/graph/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/graph/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/graph/commands.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/integration/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/integration/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/integration/commands.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/manifest/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/manifest/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/manifest/commands.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/pack/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/pack/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/pack/commands.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/playbook/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/playbook/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/playbook/commands.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/plugins/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/plugins/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/plugins/commands.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/rbac/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/rbac/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/commands/rbac/commands.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/configuration.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/error_handling/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/error_handling/connection.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/error_handling/http.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/log.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/plugins/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/plugins/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/plugins/manager.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/utilities/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/utilities/config_file.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/utilities/content.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/utilities/manifest.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/utilities/validators.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/utilities/version_check.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/artifact_providers/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/artifact_providers/azure.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/artifact_providers/base.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/artifact_providers/s3.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/cases.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/client.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/constants.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/integrations.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/packs.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/src/xsoar_cli/xsoar_client/rbac.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/__init__.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/conftest.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_base.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_case.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_config.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Download/playbook-empty.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonPlaybooks/.pack-ignore +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonPlaybooks/.secrets-ignore +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonPlaybooks/Author_image.png +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonPlaybooks/Playbooks/GenericPlaybook.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonPlaybooks/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonPlaybooks/pack_metadata.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonScripts/.pack-ignore +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonScripts/.secrets-ignore +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonScripts/Author_image.png +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonScripts/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonScripts/Scripts/GenericScript/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_CommonScripts/pack_metadata.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/.pack-ignore +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/.secrets-ignore +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Author_image.png +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Playbooks/EDR_InitialTriage.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_Triage/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/LegacyItem/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/Scripts/LegacyItem/test_data/basescript-dummy.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_EDR/pack_metadata.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_Layouts/.pack-ignore +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_Layouts/.secrets-ignore +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_Layouts/Author_image.png +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_Layouts/Layouts/layoutscontainer-GenericLayout.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_Layouts/README.md +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/MyOrg_Layouts/pack_metadata.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/Packs/Not_applicable/Playbooks/Empty.yml +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/manifest_base.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/manifest_invalid.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/manifest_with_pack_not_on_server.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/server_base_response.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/server_base_response_missing_one_pack.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/server_base_response_with_updates.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_data/server_base_response_with_updates_and_one_extra.json +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_error_handling.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_graph.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_manifest.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_pack.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_playbook.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/tests/test_plugins.py +0 -0
- {xsoar_cli-2.0.0 → xsoar_cli-2.0.1}/uv.lock +0 -0
|
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `content download` subcommand for downloading individual content items by name. Supports playbooks and layouts (`xsoar-cli content download --type playbook|layout <name>`).
|
|
12
|
+
|
|
13
|
+
## [2.0.0] - 2026-04-07
|
|
14
|
+
|
|
9
15
|
### Fixed
|
|
10
16
|
|
|
11
17
|
- Fixed typo "Uknown" in error messages for `Content.download_item`, `Content.attach_item`, and `Content.detach_item`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xsoar-cli
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Project-URL: Documentation, https://github.com/tlium/xsoar-cli#readme
|
|
5
5
|
Project-URL: Issues, https://github.com/tlium/xsoar-cli/issues
|
|
6
6
|
Project-URL: Source, https://github.com/tlium/xsoar-cli
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 0,
|
|
21
|
+
__version__ = version = '2.0.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 0, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
# Refactoring Analysis
|
|
2
|
+
|
|
3
|
+
Codebase analysis identifying areas that could or should be refactored to improve
|
|
4
|
+
maintainability, consistency, and readiness for future expansion.
|
|
5
|
+
|
|
6
|
+
Items are grouped into tiers by effort and risk. Each item includes the
|
|
7
|
+
rationale, affected files, and a concrete description of what the fix looks like.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
| # | Item | Type | Effort | Risk | Impact | Files | Reason | Status |
|
|
12
|
+
|-----|------|------|--------|------|--------|-------|--------|--------|
|
|
13
|
+
| 1.1 | `print()` in `Packs.get_outdated` | Consistency | Trivial | None | Low | `xsoar_client/packs.py` L108-109 | Breaks logging convention; domain layer should not produce user output | Done |
|
|
14
|
+
| 1.1a | Surface "pack not in artifacts repo" warning to user | Improvement | Low | Low | Medium | `commands/pack/commands.py` | Item 1.1 removed user-facing output; the CLI layer should re-surface this warning | Done |
|
|
15
|
+
| 1.2 | Missing return types in `Content` | Consistency | Trivial | None | Low | `xsoar_client/content.py` L83, L90, L97, L102 | Inconsistent with the rest of the codebase; hinders static analysis | Done |
|
|
16
|
+
| 1.3 | `list` builtin shadowed in content command | Bugfix | Trivial | None | Low | `commands/content/commands.py` L59 | Prevents use of `isinstance(..., list)` and invites subtle bugs | Done |
|
|
17
|
+
| 1.4 | "Uknown" typo in `Content` errors | Bugfix | Trivial | None | Low | `xsoar_client/content.py` L63, L73, L82 | Typo in user-facing error messages (three occurrences) | Done |
|
|
18
|
+
| 2.1 | Repeated `item_type` dispatch in `Content` | Refactor | Low | Low | Medium | `xsoar_client/content.py` L57-84 | Three methods share copy-pasted `if/else`; adding a content type means editing all three | |
|
|
19
|
+
| 2.2 | Broken temp file handling in `Packs.deploy` | Bugfix | Low | Low | Medium | `xsoar_client/packs.py` L92-93 | File never cleaned up; fails on Windows; two noqa comments suppressing real issues | Done |
|
|
20
|
+
| 2.3 | Redundant `skip_validation` logic in `Packs.deploy` | Cleanup | Low | Low | Low | `xsoar_client/packs.py` L86-91 | Both branches set `skip_validation` to the same value; `deploy` duplicates `deploy_zip` | Done |
|
|
21
|
+
| 2.4 | Eager init in S3 vs lazy init in Azure provider | Consistency | Low | Low | Medium | `artifact_providers/s3.py` L12-16, `artifact_providers/azure.py` L23-33 | Inconsistent patterns; S3 fails at construction even when provider is unused | Done |
|
|
22
|
+
| 3.1 | Domain classes lack a common base class | Refactor | Medium | Low | High | `xsoar_client/cases.py` L10, `integrations.py` L10, `rbac.py` L10, `content.py` L14, `packs.py` L18 | Five classes duplicate the same `__init__`; no shared hook for cross-cutting concerns | |
|
|
23
|
+
| 3.2 | Inconsistent `raise_for_status()` in domain classes | Robustness | Medium | Low | High | `xsoar_client/content.py` L20, `client.py` L56-78 | Some methods silently swallow HTTP errors, causing confusing downstream failures | |
|
|
24
|
+
| 3.3 | Repeated `--environment` / client setup boilerplate | Refactor | Medium | Medium | High | All `commands/*/commands.py` (see 3.3 detail below) | ~5 identical lines in every command; adding a new command requires copying boilerplate | |
|
|
25
|
+
| 3.4 | Manifest `commands.py` mixes CLI and business logic | Refactor | Medium | Low | Medium | `commands/manifest/commands.py` L25-113 (helpers), 447 lines total | 447-line file; business logic not independently testable without Click | |
|
|
26
|
+
| 3.5 | Error handling layer underutilized | Improvement | Medium | Medium | Medium | `error_handling/http.py` L8-22, `commands/case/commands.py` L55-57 | `HTTPErrorHandler` used in one command; all others handle errors ad-hoc | |
|
|
27
|
+
| 4.1 | `Packs.deploy` mixes download and upload | Refactor | Low | Low | Low | `xsoar_client/packs.py` L82-101 | Overlaps with `deploy_zip`; neither path is independently testable | |
|
|
28
|
+
| 4.2 | `validate_xsoar_connectivity` decorator complexity | Cleanup | Low | Low | Low | `utilities/validators.py` L19-68 | Accepts four input shapes; hard to predict behavior at a glance | Done |
|
|
29
|
+
| 4.3 | Test coverage gaps in command groups | Testing | High | Low | High | `tests/test_graph.py`, `test_manifest.py`, `test_pack.py`, `test_playbook.py` | Most command groups have only "exits 0" tests; prerequisite for safe refactoring | |
|
|
30
|
+
|
|
31
|
+
All file paths in the table are relative to `src/xsoar_cli/`.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Tier 1: Trivial fixes (low effort, no risk)
|
|
36
|
+
|
|
37
|
+
These are isolated corrections that can each be done in a single commit with no
|
|
38
|
+
behavioral change.
|
|
39
|
+
|
|
40
|
+
### 1.1 `Packs.get_outdated` uses `print()` instead of `logging`
|
|
41
|
+
|
|
42
|
+
**File:** `src/xsoar_cli/xsoar_client/packs.py` lines 108-109
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
except ValueError:
|
|
46
|
+
msg = f"WARNING: custom pack {pack['id']} installed on XSOAR server, ..."
|
|
47
|
+
print(msg, file=sys.stderr)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This is the only place in the entire codebase that uses `print()` for warnings.
|
|
51
|
+
Domain classes (the `xsoar_client` layer) should not produce user-facing output;
|
|
52
|
+
that responsibility belongs to the CLI layer. Using `print()` here also bypasses
|
|
53
|
+
the logging infrastructure entirely, so the warning never appears in the log file.
|
|
54
|
+
|
|
55
|
+
**Fix:** Replace with `logger.warning(...)`. If the CLI layer needs to surface
|
|
56
|
+
this to the user, it should catch the condition itself or inspect the return
|
|
57
|
+
value.
|
|
58
|
+
|
|
59
|
+
**Follow-up (1.1a):** The `print()` call was the only way this warning reached
|
|
60
|
+
the user. Now that it is logged instead, the CLI layer (`pack/commands.py` or
|
|
61
|
+
any other caller of `get_outdated()`) should detect skipped packs and display a
|
|
62
|
+
warning via `click.echo()`. This could be done by having `get_outdated()` return
|
|
63
|
+
the skipped pack IDs alongside the outdated list, or by accepting a callback.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### 1.2 Missing return type annotations in `Content`
|
|
68
|
+
|
|
69
|
+
**File:** `src/xsoar_cli/xsoar_client/content.py`
|
|
70
|
+
|
|
71
|
+
Four methods lack return type annotations:
|
|
72
|
+
|
|
73
|
+
| Method | Line | Suggested return type |
|
|
74
|
+
|--------|------|----------------------|
|
|
75
|
+
| `_list_playbooks` | L83 | `list[dict]` |
|
|
76
|
+
| `_list_scripts` | L90 | `list[dict]` |
|
|
77
|
+
| `_list_commands` | L97 | `list[dict]` |
|
|
78
|
+
| `list` | L102 | `list[dict] \| dict[str, list[dict]]` |
|
|
79
|
+
|
|
80
|
+
The rest of the codebase consistently uses type hints. These should follow suit.
|
|
81
|
+
|
|
82
|
+
**Fix:** Add explicit return types to all four methods.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### 1.3 Content command function shadows Python builtin `list`
|
|
87
|
+
|
|
88
|
+
**File:** `src/xsoar_cli/commands/content/commands.py` line 59
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
def list(ctx, environment, content_type, detail, verbose):
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The function name shadows the Python builtin `list`. The file itself
|
|
95
|
+
acknowledges the problem with a workaround comment at lines 71-72:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# Note: isinstance(json_blob, list) cannot be used here because the function
|
|
99
|
+
# name "list" shadows the builtin.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Fix:** Rename the function to `list_content` (or similar) and use
|
|
103
|
+
`@click.command("list")` to preserve the CLI-facing name. This removes the
|
|
104
|
+
need for the workaround comment and prevents future bugs from the shadowed
|
|
105
|
+
builtin.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### 1.4 Typo: "Uknown" in `Content` error messages
|
|
110
|
+
|
|
111
|
+
**File:** `src/xsoar_cli/xsoar_client/content.py`
|
|
112
|
+
|
|
113
|
+
The string `"Uknown"` appears in three `ValueError` messages:
|
|
114
|
+
|
|
115
|
+
| Line | Method |
|
|
116
|
+
|------|--------|
|
|
117
|
+
| L63 | `download_item` |
|
|
118
|
+
| L73 | `attach_item` |
|
|
119
|
+
| L82 | `detach_item` |
|
|
120
|
+
|
|
121
|
+
Should be `"Unknown"`.
|
|
122
|
+
|
|
123
|
+
**Fix:** Fix the typo in all three places (related to item 2.1 below, which
|
|
124
|
+
refactors the dispatch pattern these messages live in).
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Tier 2: Small refactors (low effort, low risk)
|
|
129
|
+
|
|
130
|
+
Each of these touches one or two files and can be done independently.
|
|
131
|
+
|
|
132
|
+
### 2.1 Repeated `item_type` dispatch in `Content`
|
|
133
|
+
|
|
134
|
+
**File:** `src/xsoar_cli/xsoar_client/content.py` lines 57-84
|
|
135
|
+
|
|
136
|
+
Three methods (`download_item` at L57, `attach_item` at L67, `detach_item` at
|
|
137
|
+
L77) share the exact same structure:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
def download_item(self, item_type: str, item_id: str) -> bytes:
|
|
141
|
+
if item_type == "playbook":
|
|
142
|
+
endpoint = f"/{item_type}/{item_id}/yaml"
|
|
143
|
+
response = self.client.make_request(endpoint=endpoint, method="GET")
|
|
144
|
+
else:
|
|
145
|
+
msg = 'Uknown item_type selected for download. Must be one of ["playbook"]'
|
|
146
|
+
raise ValueError(msg)
|
|
147
|
+
response.raise_for_status()
|
|
148
|
+
return response.content
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The `if item_type == "playbook" ... else raise ValueError` block is copy-pasted
|
|
152
|
+
three times. When a new content type is added (e.g. `"script"`), all three
|
|
153
|
+
methods need to be updated in lockstep, which is error-prone.
|
|
154
|
+
|
|
155
|
+
**Fix options (pick one):**
|
|
156
|
+
|
|
157
|
+
- **Validation helper:** Extract a `_validate_item_type(item_type)` method that
|
|
158
|
+
raises `ValueError` for unsupported types. Each method calls it once and then
|
|
159
|
+
proceeds with endpoint construction without the `if/else`.
|
|
160
|
+
|
|
161
|
+
- **Endpoint map:** A class-level dict mapping `(action, item_type)` to endpoint
|
|
162
|
+
templates. Each method does a lookup and raises if the key is missing. Adding
|
|
163
|
+
a new content type becomes a single dict entry.
|
|
164
|
+
|
|
165
|
+
Either approach also fixes the typo from item 1.4.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### 2.2 Temporary file handling in `Packs.deploy`
|
|
170
|
+
|
|
171
|
+
**File:** `src/xsoar_cli/xsoar_client/packs.py` lines 92-93
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
tmp = tempfile.NamedTemporaryFile() # noqa: SIM115
|
|
175
|
+
with open(tmp.name, "wb") as f: # noqa: PTH123
|
|
176
|
+
f.write(filedata)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Problems:
|
|
180
|
+
|
|
181
|
+
1. `NamedTemporaryFile` is created but never used as a context manager. The file
|
|
182
|
+
object is not explicitly closed or cleaned up.
|
|
183
|
+
2. The file is opened a second time via `open(tmp.name, "wb")`, which on Windows
|
|
184
|
+
would fail because `NamedTemporaryFile` keeps the file open by default.
|
|
185
|
+
3. Two `noqa` comments suppress linter warnings that are flagging real issues.
|
|
186
|
+
|
|
187
|
+
**Fix:**
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp:
|
|
191
|
+
tmp.write(filedata)
|
|
192
|
+
tmp.flush()
|
|
193
|
+
tmp_path = tmp.name
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
self.client.demisto_py_instance.upload_content_packs(tmp_path, **params)
|
|
197
|
+
except ApiException as ex:
|
|
198
|
+
...
|
|
199
|
+
finally:
|
|
200
|
+
Path(tmp_path).unlink(missing_ok=True)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This also eliminates the need for the two `noqa` comments.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### 2.3 `Packs.deploy` duplicates `skip_validation`/`skip_verify` logic
|
|
208
|
+
|
|
209
|
+
**File:** `src/xsoar_cli/xsoar_client/packs.py` lines 86-91
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
if custom:
|
|
213
|
+
params["skip_validation"] = "false"
|
|
214
|
+
params["skip_verify"] = "true"
|
|
215
|
+
else:
|
|
216
|
+
params["skip_validation"] = "false"
|
|
217
|
+
params["skip_verify"] = "false"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`skip_validation` is `"false"` in both branches. Only `skip_verify` differs.
|
|
221
|
+
This can be simplified to:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
params = {
|
|
225
|
+
"skip_validation": "false",
|
|
226
|
+
"skip_verify": "true" if custom else "false",
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The existing `deploy_zip` method (L79-85) also accepts `skip_validation`/
|
|
231
|
+
`skip_verify` as booleans and converts them to strings internally. The two
|
|
232
|
+
methods overlap in responsibility. Consider having `deploy` call `deploy_zip`
|
|
233
|
+
after downloading, rather than reimplementing the upload step.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### 2.4 S3 provider eager init vs Azure lazy init
|
|
238
|
+
|
|
239
|
+
**Files:**
|
|
240
|
+
- `src/xsoar_cli/xsoar_client/artifact_providers/s3.py` lines 12-16
|
|
241
|
+
- `src/xsoar_cli/xsoar_client/artifact_providers/azure.py` lines 23-33
|
|
242
|
+
|
|
243
|
+
`S3ArtifactProvider.__init__` eagerly creates the boto3 session and S3 resource:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
# s3.py L12-16
|
|
247
|
+
def __init__(self, *, bucket_name, verify_ssl=True):
|
|
248
|
+
self.bucket_name = bucket_name
|
|
249
|
+
self.verify_ssl = verify_ssl
|
|
250
|
+
self.session = boto3.session.Session()
|
|
251
|
+
self.s3 = self.session.resource("s3")
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`AzureArtifactProvider` uses lazy `@property` accessors that defer creation
|
|
255
|
+
until first use (L23-33).
|
|
256
|
+
|
|
257
|
+
This inconsistency means:
|
|
258
|
+
|
|
259
|
+
- S3 provider creation fails immediately if AWS credentials are missing, even if
|
|
260
|
+
the current command does not need the artifact provider.
|
|
261
|
+
- Azure defers the failure to the point of actual use, which is the better
|
|
262
|
+
behavior.
|
|
263
|
+
|
|
264
|
+
**Fix:** Make S3 follow the same lazy pattern as Azure. Store `bucket_name` and
|
|
265
|
+
`verify_ssl` in `__init__`, create the session/resource in a `@property`.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Tier 3: Medium refactors (medium effort, low-to-medium risk)
|
|
270
|
+
|
|
271
|
+
These touch multiple files or introduce new abstractions. Each should be done as
|
|
272
|
+
an isolated step.
|
|
273
|
+
|
|
274
|
+
### 3.1 Domain classes lack a common base class
|
|
275
|
+
|
|
276
|
+
**Files** (all under `src/xsoar_cli/xsoar_client/`):
|
|
277
|
+
|
|
278
|
+
| Class | File | `__init__` line |
|
|
279
|
+
|-------|------|-----------------|
|
|
280
|
+
| `Cases` | `cases.py` | L10 |
|
|
281
|
+
| `Content` | `content.py` | L14 |
|
|
282
|
+
| `Integrations` | `integrations.py` | L10 |
|
|
283
|
+
| `Packs` | `packs.py` | L18 |
|
|
284
|
+
| `Rbac` | `rbac.py` | L10 |
|
|
285
|
+
|
|
286
|
+
All five domain classes follow the same pattern:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
class Cases:
|
|
290
|
+
def __init__(self, client: Client) -> None:
|
|
291
|
+
self.client = client
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
A `BaseDomain` class would:
|
|
295
|
+
|
|
296
|
+
- Eliminate the duplicated `__init__` in each class.
|
|
297
|
+
- Provide a natural place to add shared behavior later (automatic
|
|
298
|
+
`raise_for_status()`, response logging, retry logic, request tracing).
|
|
299
|
+
- Make it explicit that these classes share a contract.
|
|
300
|
+
|
|
301
|
+
**Sketch:**
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
class BaseDomain:
|
|
305
|
+
def __init__(self, client: Client) -> None:
|
|
306
|
+
self.client = client
|
|
307
|
+
|
|
308
|
+
class Cases(BaseDomain):
|
|
309
|
+
def get(self, case_id: int) -> dict:
|
|
310
|
+
...
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`Packs` has a slightly different `__init__` (extra `custom_pack_authors`
|
|
314
|
+
parameter). It would call `super().__init__(client)` and then set its own
|
|
315
|
+
attributes.
|
|
316
|
+
|
|
317
|
+
The base class could live in a new file `src/xsoar_cli/xsoar_client/base.py`
|
|
318
|
+
or in the existing `__init__.py`.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### 3.2 Inconsistent HTTP response error handling in domain classes
|
|
323
|
+
|
|
324
|
+
**Files:** All domain classes under `src/xsoar_cli/xsoar_client/`,
|
|
325
|
+
`client.py` L56-78 (`make_request`)
|
|
326
|
+
|
|
327
|
+
Some methods call `response.raise_for_status()`, some don't:
|
|
328
|
+
|
|
329
|
+
| Method | File | Line | Calls `raise_for_status()`? |
|
|
330
|
+
|--------|------|------|-----------------------------|
|
|
331
|
+
| `Cases.get()` | `cases.py` | L13 | Yes |
|
|
332
|
+
| `Cases.create()` | `cases.py` | L20 | Yes |
|
|
333
|
+
| `Content.get_bundle()` | `content.py` | L17 | **No** |
|
|
334
|
+
| `Content.get_detached()` | `content.py` | L38 | Yes |
|
|
335
|
+
| `Packs.get_installed()` | `packs.py` | L24 | Yes |
|
|
336
|
+
| `Rbac.get_users()` | `rbac.py` | L14 | Yes |
|
|
337
|
+
|
|
338
|
+
If `Content.get_bundle()` receives a non-2xx response, it silently tries to
|
|
339
|
+
untar the error body and fails with a confusing `tarfile` error rather than a
|
|
340
|
+
clear HTTP error.
|
|
341
|
+
|
|
342
|
+
**Fix options:**
|
|
343
|
+
|
|
344
|
+
1. **Audit and add:** Go through every method, add `raise_for_status()` where
|
|
345
|
+
missing. Simple but requires ongoing discipline.
|
|
346
|
+
|
|
347
|
+
2. **Default-safe `make_request`:** Add a `raise_for_status=True` parameter to
|
|
348
|
+
`Client.make_request()` (L56) so the default is safe. Methods that need the
|
|
349
|
+
raw response can opt out. This ties in well with a `BaseDomain` class
|
|
350
|
+
(item 3.1) that could wrap requests uniformly.
|
|
351
|
+
|
|
352
|
+
Option 2 is preferable because it makes the safe path the default and shifts
|
|
353
|
+
the burden to the rare cases that need raw responses.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### 3.3 Repeated `--environment` option and client setup boilerplate
|
|
358
|
+
|
|
359
|
+
**Files:** Every `commands.py` under `src/xsoar_cli/commands/`
|
|
360
|
+
|
|
361
|
+
Almost every command repeats this pattern:
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
@click.option("--environment", default=None, help="Default environment set in config file.")
|
|
365
|
+
@click.pass_context
|
|
366
|
+
@load_config
|
|
367
|
+
@validate_xsoar_connectivity()
|
|
368
|
+
def some_command(ctx, environment, ...):
|
|
369
|
+
config = get_xsoar_config(ctx)
|
|
370
|
+
xsoar_client: Client = config.get_client(environment)
|
|
371
|
+
active_env = environment or config.default_environment
|
|
372
|
+
...
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
The `--environment` option appears at the following locations:
|
|
376
|
+
|
|
377
|
+
| File | Lines | Commands |
|
|
378
|
+
|------|-------|----------|
|
|
379
|
+
| `commands/case/commands.py` | L36, L132 | `get`, `create` |
|
|
380
|
+
| `commands/pack/commands.py` | L23, L47 | `delete`, `get_outdated` |
|
|
381
|
+
| `commands/rbac/commands.py` | L24, L34, L44 | `getroles`, `getusers`, `getusergroups` |
|
|
382
|
+
| `commands/manifest/commands.py` | L122, L153, L229, L300, L356 | `generate`, `update`, `validate`, `diff`, `deploy` |
|
|
383
|
+
| `commands/content/commands.py` | L23, L52 | `get_detached`, `list` |
|
|
384
|
+
| `commands/integration/commands.py` | L24 | `dump` |
|
|
385
|
+
| `commands/playbook/commands.py` | L26 | `download` |
|
|
386
|
+
| `commands/graph/commands.py` | L30 | All graph subcommands (via `_common_graph_options`) |
|
|
387
|
+
|
|
388
|
+
That is 16 occurrences of the same option definition, plus 15+ repetitions of
|
|
389
|
+
the `get_xsoar_config` / `get_client` / `active_env` setup block.
|
|
390
|
+
|
|
391
|
+
**Fix:** Create a shared decorator (e.g., `@xsoar_command`) that:
|
|
392
|
+
|
|
393
|
+
1. Adds `--environment` as a Click option.
|
|
394
|
+
2. Applies `@load_config`.
|
|
395
|
+
3. Resolves `config`, `xsoar_client`, and `active_env` and passes them to the
|
|
396
|
+
decorated function (either via context or as injected parameters).
|
|
397
|
+
|
|
398
|
+
Optionally, this decorator could accept flags to also apply
|
|
399
|
+
`@validate_xsoar_connectivity()` and/or `@validate_artifacts_provider`.
|
|
400
|
+
|
|
401
|
+
This would reduce each command to its unique logic and make adding new commands
|
|
402
|
+
less error-prone. The decorator should live in `utilities/` alongside the
|
|
403
|
+
existing `load_config`.
|
|
404
|
+
|
|
405
|
+
Note that `graph/commands.py` already partially solves this with
|
|
406
|
+
`_common_graph_options` (L22-34). A project-wide decorator would subsume that
|
|
407
|
+
pattern.
|
|
408
|
+
|
|
409
|
+
**Risk note:** This touches many files but each change is mechanical. It should
|
|
410
|
+
be done in one pass to keep the pattern consistent, with the test suite
|
|
411
|
+
validating each command still works.
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
### 3.4 Extract manifest business logic from `commands.py`
|
|
416
|
+
|
|
417
|
+
**File:** `src/xsoar_cli/commands/manifest/commands.py` (447 lines)
|
|
418
|
+
|
|
419
|
+
This is the largest command file by a wide margin. It contains both CLI wiring
|
|
420
|
+
(Click decorators, `click.echo()` calls, `ctx.exit()`) and business logic
|
|
421
|
+
(manifest file I/O, version comparison loops, pack availability checking).
|
|
422
|
+
|
|
423
|
+
The following helpers could move to `src/xsoar_cli/utilities/manifest.py`:
|
|
424
|
+
|
|
425
|
+
| Function | Current location | Lines |
|
|
426
|
+
|----------|-----------------|-------|
|
|
427
|
+
| `load_manifest` | `manifest/commands.py` | L25-34 |
|
|
428
|
+
| `write_manifest` | `manifest/commands.py` | L37-43 |
|
|
429
|
+
| `_pack_found_locally` | `manifest/commands.py` | L46-59 |
|
|
430
|
+
| `_validate_manifest_keys` | `manifest/commands.py` | L62-73 |
|
|
431
|
+
| `_check_pack_availability` | `manifest/commands.py` | L76-113 |
|
|
432
|
+
|
|
433
|
+
`utilities/manifest.py` already holds some manifest-related logic
|
|
434
|
+
(`find_installed_packs_not_in_manifest`, `find_packs_in_manifest_not_installed`,
|
|
435
|
+
`find_version_mismatch`). The split is partially done. Completing it would:
|
|
436
|
+
|
|
437
|
+
- Keep `commands.py` focused on Click wiring and user-facing output.
|
|
438
|
+
- Make the business logic independently testable without invoking Click.
|
|
439
|
+
- Reduce the file to a more reviewable size.
|
|
440
|
+
|
|
441
|
+
`_check_pack_availability` currently calls `click.echo()` for progress dots and
|
|
442
|
+
raises `click.ClickException`. To move it cleanly, it should either accept a
|
|
443
|
+
progress callback or return results that the command layer formats. Similarly,
|
|
444
|
+
`load_manifest` currently raises `click.ClickException`; it should raise a plain
|
|
445
|
+
exception that the command layer catches and converts.
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
### 3.5 Error handling layer is underutilized
|
|
450
|
+
|
|
451
|
+
**Files:**
|
|
452
|
+
- `src/xsoar_cli/error_handling/http.py` L8-22 (`HTTPErrorHandler` class and `_messages` dict)
|
|
453
|
+
- `src/xsoar_cli/error_handling/connection.py` L9-56 (`ConnectionErrorHandler`)
|
|
454
|
+
- All command modules
|
|
455
|
+
|
|
456
|
+
The `error_handling/` package defines two well-structured handler classes:
|
|
457
|
+
|
|
458
|
+
- `HTTPErrorHandler` (L8): maps HTTP status codes to user-friendly messages,
|
|
459
|
+
with per-context customization via `_messages` (L17-22).
|
|
460
|
+
- `ConnectionErrorHandler`: extracts actionable messages from exception chains.
|
|
461
|
+
|
|
462
|
+
However, `HTTPErrorHandler` is only used in `commands/case/commands.py` at
|
|
463
|
+
L55-57:
|
|
464
|
+
|
|
465
|
+
```python
|
|
466
|
+
except HTTPError as ex:
|
|
467
|
+
handler = HTTPErrorHandler()
|
|
468
|
+
click.echo(f"Error: {handler.get_message(ex, context='case')}")
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
All other commands that handle HTTP errors do so ad-hoc:
|
|
472
|
+
|
|
473
|
+
- `commands/manifest/commands.py` `deploy` (L397-415): catches `RuntimeError`
|
|
474
|
+
wrapping `ApiException`, manually parses the JSON error body.
|
|
475
|
+
- `commands/pack/commands.py`, `commands/rbac/commands.py`,
|
|
476
|
+
`commands/integration/commands.py`: let exceptions from `raise_for_status()`
|
|
477
|
+
propagate uncaught (Click prints the traceback).
|
|
478
|
+
|
|
479
|
+
`ConnectionErrorHandler` is only used in `utilities/validators.py` L59.
|
|
480
|
+
|
|
481
|
+
**Fix:** Expand the `HTTPErrorHandler._messages` dict with entries for contexts
|
|
482
|
+
used by other commands (`"pack"`, `"manifest"`, `"integration"`, etc.) and
|
|
483
|
+
apply the handler consistently. This could also be tied into the common
|
|
484
|
+
decorator from item 3.3, where a top-level exception handler wraps every
|
|
485
|
+
command invocation.
|
|
486
|
+
|
|
487
|
+
This is a gradual process. No need to do it all at once, but each new command
|
|
488
|
+
should use the error handling layer from the start.
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Tier 4: Larger structural improvements (higher effort)
|
|
493
|
+
|
|
494
|
+
These are not urgent but would pay off as the codebase grows.
|
|
495
|
+
|
|
496
|
+
### 4.1 `Packs.deploy` mixes download and upload responsibilities
|
|
497
|
+
|
|
498
|
+
**File:** `src/xsoar_cli/xsoar_client/packs.py` lines 82-101
|
|
499
|
+
|
|
500
|
+
The `deploy` method:
|
|
501
|
+
|
|
502
|
+
1. Downloads the pack (via `self.download()`, called at L84).
|
|
503
|
+
2. Determines `skip_validation`/`skip_verify` params (L86-91).
|
|
504
|
+
3. Writes data to a temp file (L92-93).
|
|
505
|
+
4. Uploads it (via `demisto_py_instance.upload_content_packs()`, L96).
|
|
506
|
+
|
|
507
|
+
Meanwhile, `deploy_zip` (L79-85) exists as a separate method that handles step 4
|
|
508
|
+
from a user-provided file path. The two methods overlap but don't share code.
|
|
509
|
+
|
|
510
|
+
**Fix:** Have `deploy` download the pack, write it to a temp file, and then
|
|
511
|
+
call `deploy_zip` for the upload step. This avoids duplicating the
|
|
512
|
+
`upload_content_packs` call and its error handling, and makes both paths
|
|
513
|
+
independently testable.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
### 4.2 `validate_xsoar_connectivity` decorator complexity
|
|
518
|
+
|
|
519
|
+
**File:** `src/xsoar_cli/utilities/validators.py` lines 19-68
|
|
520
|
+
|
|
521
|
+
The `validate_xsoar_connectivity` decorator accepts environments as `None`,
|
|
522
|
+
`str`, `list[str]`, or `Callable` (union type at L20-21):
|
|
523
|
+
|
|
524
|
+
```python
|
|
525
|
+
def validate_xsoar_connectivity(
|
|
526
|
+
environments: str | list[str] | Callable[[click.Context], str | list[str]] | None = None,
|
|
527
|
+
) -> Callable:
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
This flexibility is powerful but makes the decorator's behavior hard to predict
|
|
531
|
+
at a glance. The `Callable` variant is used only once, in
|
|
532
|
+
`commands/case/commands.py` for the `clone` command:
|
|
533
|
+
|
|
534
|
+
```python
|
|
535
|
+
@validate_xsoar_connectivity(lambda ctx: [ctx.params["source"], ctx.params["dest"]])
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
If the common decorator from item 3.3 is introduced, the multi-environment
|
|
539
|
+
validation could be handled more explicitly (e.g., the decorator accepts a list
|
|
540
|
+
of parameter names to resolve environments from). This would simplify the type
|
|
541
|
+
signature and make the intent clearer.
|
|
542
|
+
|
|
543
|
+
Not urgent, but worth revisiting if item 3.3 is implemented.
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
### 4.3 Test coverage gaps
|
|
548
|
+
|
|
549
|
+
**Files:**
|
|
550
|
+
|
|
551
|
+
| Test file | Commands covered | Depth |
|
|
552
|
+
|-----------|-----------------|-------|
|
|
553
|
+
| `tests/test_config.py` | `config validate` | Thorough (7 test classes, edge cases) |
|
|
554
|
+
| `tests/test_case.py` | `case get`, `case create`, `case clone` | Good (success + error paths) |
|
|
555
|
+
| `tests/test_error_handling.py` | Error handler classes | Good (parametrized) |
|
|
556
|
+
| `tests/test_plugins.py` | Plugin system | Good (unit + integration) |
|
|
557
|
+
| `tests/test_base.py` | Root CLI group | Minimal (3 parametrized cases) |
|
|
558
|
+
| `tests/test_graph.py` | `graph` | Minimal (single "exits 0" test) |
|
|
559
|
+
| `tests/test_manifest.py` | `manifest` | Minimal (single "exits 0" test) |
|
|
560
|
+
| `tests/test_pack.py` | `pack` | Minimal (single "exits 0" test) |
|
|
561
|
+
| `tests/test_playbook.py` | `playbook` | Minimal (single "exits 0" test) |
|
|
562
|
+
|
|
563
|
+
The `test_config.py` and `test_case.py` files demonstrate how to properly test
|
|
564
|
+
commands with mocked clients and config. The same patterns should be applied to
|
|
565
|
+
the other command groups.
|
|
566
|
+
|
|
567
|
+
This is not a refactoring item per se, but test coverage is a prerequisite for
|
|
568
|
+
safely performing the refactors listed above. Expanding tests for `manifest`,
|
|
569
|
+
`pack`, and `playbook` commands before refactoring those modules reduces the
|
|
570
|
+
risk of regressions.
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Suggested execution order
|
|
575
|
+
|
|
576
|
+
The items below are ordered to minimize risk and maximize incremental value.
|
|
577
|
+
Each step should be a separate commit or PR.
|
|
578
|
+
|
|
579
|
+
1. **Tier 1 quick wins** (items 1.1 through 1.4). These are independent and can
|
|
580
|
+
all be done in a single batch.
|
|
581
|
+
2. **Tier 2 small refactors** (items 2.1 through 2.4). Each is independent and
|
|
582
|
+
can be done in any order.
|
|
583
|
+
3. **Item 3.1** (domain base class). Introduces the abstraction that item 3.2
|
|
584
|
+
depends on.
|
|
585
|
+
4. **Item 3.2** (consistent `raise_for_status()`). Builds on the base class.
|
|
586
|
+
5. **Item 3.4** (extract manifest business logic). Independent of items 3.1-3.2.
|
|
587
|
+
6. **Item 3.3** (common `--environment` decorator). Largest single change. Should
|
|
588
|
+
come after the simpler refactors are settled and test coverage is solid.
|
|
589
|
+
7. **Item 3.5** (standardize error handling). Gradual, can be done command by
|
|
590
|
+
command alongside other work.
|
|
591
|
+
8. **Tier 4 items** as opportunities arise.
|
|
@@ -64,14 +64,15 @@ def cli(ctx: click.Context, debug: bool) -> None:
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
def _register_commands() -> None:
|
|
67
|
-
cli.add_command(config_commands.config)
|
|
68
67
|
cli.add_command(case_commands.case)
|
|
68
|
+
cli.add_command(config_commands.config)
|
|
69
69
|
cli.add_command(content_commands.content)
|
|
70
|
-
|
|
70
|
+
|
|
71
|
+
cli.add_command(graph_commands.graph)
|
|
71
72
|
cli.add_command(integration_commands.integration)
|
|
72
73
|
cli.add_command(manifest_commands.manifest)
|
|
74
|
+
cli.add_command(pack_commands.pack)
|
|
73
75
|
cli.add_command(playbook_commands.playbook)
|
|
74
|
-
cli.add_command(graph_commands.graph)
|
|
75
76
|
cli.add_command(plugin_commands.plugins)
|
|
76
77
|
cli.add_command(rbac_commands.rbac)
|
|
77
78
|
|