xsoar-cli 2.2.0__tar.gz → 2.3.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.
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/CHANGELOG.md +23 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/CLAUDE.md +6 -4
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/PKG-INFO +1 -1
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/_version.py +2 -2
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/cli.py +40 -3
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/content/README.md +2 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/content/commands.py +14 -8
- xsoar_cli-2.3.0/src/xsoar_cli/commands/execute/README.md +67 -0
- xsoar_cli-2.3.0/src/xsoar_cli/commands/execute/commands.py +337 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/manifest/README.md +4 -1
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/manifest/commands.py +36 -35
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/content.py +18 -18
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/client.py +2 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/content.py +35 -9
- xsoar_cli-2.3.0/src/xsoar_cli/xsoar_client/execution.py +260 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/conftest.py +37 -0
- xsoar_cli-2.3.0/tests/cli/test_base.py +71 -0
- xsoar_cli-2.3.0/tests/cli/test_execute.py +287 -0
- xsoar_cli-2.3.0/tests/cli/test_manifest.py +225 -0
- xsoar_cli-2.3.0/tests/unit/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_content_domain.py +36 -5
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_content_filters.py +7 -7
- xsoar_cli-2.3.0/tests/unit/test_execution.py +225 -0
- xsoar_cli-2.2.0/tests/cli/test_base.py +0 -17
- xsoar_cli-2.2.0/tests/cli/test_manifest.py +0 -18
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.github/workflows/main.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.github/workflows/pull-request-open.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.github/workflows/release.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.github/workflows/update-changelog.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/.gitignore +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/CONTRIBUTING.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/LICENSE.txt +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/pyproject.toml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/case/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/case/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/case/commands.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/completions/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/completions/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/completions/commands.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/config/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/config/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/config/commands.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/content/__init__.py +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/commands/graph → xsoar_cli-2.3.0/src/xsoar_cli/commands/execute}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/graph/README.md +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/commands/integration → xsoar_cli-2.3.0/src/xsoar_cli/commands/graph}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/graph/commands.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/integration/README.md +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/commands/manifest → xsoar_cli-2.3.0/src/xsoar_cli/commands/integration}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/integration/commands.py +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/commands/pack → xsoar_cli-2.3.0/src/xsoar_cli/commands/manifest}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/pack/README.md +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/commands/plugins → xsoar_cli-2.3.0/src/xsoar_cli/commands/pack}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/pack/commands.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/plugins/README.md +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/commands/rbac → xsoar_cli-2.3.0/src/xsoar_cli/commands/plugins}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/plugins/commands.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/rbac/README.md +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/error_handling → xsoar_cli-2.3.0/src/xsoar_cli/commands/rbac}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/commands/rbac/commands.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/configuration.py +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/utilities → xsoar_cli-2.3.0/src/xsoar_cli/error_handling}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/error_handling/connection.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/error_handling/http.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/log.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/plugins/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/plugins/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/plugins/manager.py +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/xsoar_client → xsoar_cli-2.3.0/src/xsoar_cli/utilities}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/config_file.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/download_content_handlers.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/manifest.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/validators.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/utilities/version_check.py +0 -0
- {xsoar_cli-2.2.0/src/xsoar_cli/xsoar_client/artifact_providers → xsoar_cli-2.3.0/src/xsoar_cli/xsoar_client}/__init__.py +0 -0
- {xsoar_cli-2.2.0/tests/cli → xsoar_cli-2.3.0/src/xsoar_cli/xsoar_client/artifact_providers}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/artifact_providers/azure.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/artifact_providers/base.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/artifact_providers/s3.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/cases.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/constants.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/integrations.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/packs.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/src/xsoar_cli/xsoar_client/rbac.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/__init__.py +0 -0
- {xsoar_cli-2.2.0/tests/unit → xsoar_cli-2.3.0/tests/cli}/__init__.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_case.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_completions.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_config.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_content.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_graph.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_pack.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/cli/test_plugins.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/conftest.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/cases/create.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/cases/get.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/content/automation_search.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/content/playbook_search.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/content/user_commands.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/integrations/instances.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/manifest_base.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/manifest_invalid.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/manifest_with_pack_not_on_server.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/server_base_response.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/server_base_response_missing_one_pack.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/server_base_response_with_updates.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/manifest/server_base_response_with_updates_and_one_extra.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/packs/installed.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/packs/installed_expired.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/rbac/roles.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/rbac/user_groups.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/fixtures/rbac/users.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Download/playbook-empty.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/.pack-ignore +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/.secrets-ignore +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/Author_image.png +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/Playbooks/GenericPlaybook.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/pack_metadata.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/.pack-ignore +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/.secrets-ignore +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Author_image.png +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_CommonScripts/pack_metadata.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/.pack-ignore +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/.secrets-ignore +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Author_image.png +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Playbooks/EDR_InitialTriage.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/test_data/basescript-dummy.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_EDR/pack_metadata.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/.pack-ignore +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/.secrets-ignore +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/Author_image.png +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/Layouts/layoutscontainer-GenericLayout.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/README.md +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/MyOrg_Layouts/pack_metadata.json +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/mock_content/Packs/Not_applicable/Playbooks/Empty.yml +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/conftest.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_artifact_azure.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_artifact_base.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_artifact_s3.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_cases.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_client.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_config_file.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_content_handlers.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_error_handling.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_integrations.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_main.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_manifest_utils.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_packs.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_plugin_manager.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_rbac.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_validators.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/tests/unit/test_version_check.py +0 -0
- {xsoar_cli-2.2.0 → xsoar_cli-2.3.0}/uv.lock +0 -0
|
@@ -8,6 +8,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
8
8
|
|
|
9
9
|
### Added
|
|
10
10
|
|
|
11
|
+
- `execute command NAME [ARGS]...` runs an automation script or integration command against XSOAR, in the user's playground by default or a specific case with `--case-id`. See the [execute README](src/xsoar_cli/commands/execute/README.md) for details.
|
|
12
|
+
- `execute playbook NAME` starts a playbook in the user's playground, or a specific case with `--case-id`. See the [execute README](src/xsoar_cli/commands/execute/README.md) for details.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `content download` now correctly resolves playbooks whose ID differs from their name when the name contains spaces. Previously, the name search query was not quoted, causing it to return no results. In practice this affected custom playbooks created in the XSOAR UI, which use a UUID as their ID.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Breaking:** `manifest validate` now validates all packs by default (previously defaulted to `--mode diff`). Use the new `--only-changed` flag to validate only packs that differ from what is installed on the server.
|
|
21
|
+
- **Breaking:** `content list` now uses a single `--detail-level` option (choices: `short`, `extended`, `full`, default: `short`) instead of the separate `--details` and `--verbose` flags.
|
|
22
|
+
- Plugin commands are now listed under a separate `Plugins:` section in `--help` output instead of being mixed with core commands.
|
|
23
|
+
|
|
24
|
+
### Removed
|
|
25
|
+
|
|
26
|
+
- `manifest validate --mode` option. Use `--only-changed` instead of `--mode diff`. Full validation is now the default.
|
|
27
|
+
- `content list --details` flag. Use `--detail-level extended` instead.
|
|
28
|
+
- `content list --verbose` flag. Use `--detail-level full` instead.
|
|
29
|
+
|
|
30
|
+
## [2.2.0] - 2026-04-10
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
11
34
|
- `config set-version-check` command to enable or disable the version update check on CLI startup. Accepts `--enable` or `--disable`.
|
|
12
35
|
- `plugins init` command to create the plugins directory and generate an example plugin.
|
|
13
36
|
|
|
@@ -28,7 +28,7 @@ for confirmation before executing any terminal commands as they may have consequ
|
|
|
28
28
|
- **Build System**: Hatchling (`pyproject.toml`)
|
|
29
29
|
- **Package Manager**: uv (`uv.lock`)
|
|
30
30
|
- **Linting**: Ruff
|
|
31
|
-
- **Formatting**: Black
|
|
31
|
+
- **Formatting**: Ruff formatter (>99.9% Black-compatible)
|
|
32
32
|
- **Testing**: pytest, pytest-cov
|
|
33
33
|
- **CI**: GitHub Actions (Python 3.10-3.14)
|
|
34
34
|
|
|
@@ -142,10 +142,10 @@ uv run pytest tests/cli/
|
|
|
142
142
|
uv run pytest tests/unit/
|
|
143
143
|
|
|
144
144
|
# Format code
|
|
145
|
-
|
|
145
|
+
uv run ruff format src/ tests/
|
|
146
146
|
|
|
147
147
|
# Lint
|
|
148
|
-
ruff check src/ tests/
|
|
148
|
+
uv run ruff check src/ tests/
|
|
149
149
|
|
|
150
150
|
# Run the CLI
|
|
151
151
|
uv run xsoar-cli --help
|
|
@@ -206,9 +206,11 @@ The user's live configuration at `~/.config/xsoar-cli/config.json` contains API
|
|
|
206
206
|
|
|
207
207
|
## Changelog
|
|
208
208
|
|
|
209
|
+
**Every PR that changes user-facing behavior MUST include a `CHANGELOG.md` update. Do not treat this as optional or as a follow-up step. The changelog entry is part of the implementation, not separate from it.**
|
|
210
|
+
|
|
209
211
|
- The project maintains a `CHANGELOG.md` in the repository root.
|
|
210
212
|
- The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
211
|
-
-
|
|
213
|
+
- Changes that require a changelog entry:
|
|
212
214
|
- Adding, removing, or renaming commands, options, or arguments (`Added` / `Removed`)
|
|
213
215
|
- Changes to default behavior or output format (`Changed`)
|
|
214
216
|
- Bug fixes (`Fixed`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xsoar-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A command-line interface for managing Palo Alto Networks XSOAR (Cortex XSOAR)
|
|
5
5
|
Project-URL: Documentation, https://github.com/tlium/xsoar-cli#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/tlium/xsoar-cli/issues
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 3, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -17,6 +17,7 @@ from .commands.case import commands as case_commands
|
|
|
17
17
|
from .commands.completions import commands as completions_commands
|
|
18
18
|
from .commands.config import commands as config_commands
|
|
19
19
|
from .commands.content import commands as content_commands
|
|
20
|
+
from .commands.execute import commands as execute_commands
|
|
20
21
|
from .commands.graph import commands as graph_commands
|
|
21
22
|
from .commands.integration import commands as integration_commands
|
|
22
23
|
from .commands.manifest import commands as manifest_commands
|
|
@@ -30,12 +31,46 @@ from .utilities.version_check import check_for_update
|
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
class XSOARCliGroup(click.Group):
|
|
33
|
-
"""Custom Click group
|
|
34
|
+
"""Custom Click group with separate help sections for core and plugin commands.
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
"No such command" error with no indication that a
|
|
36
|
+
Also surfaces plugin load failures when a command is not found, so the user
|
|
37
|
+
does not get a generic "No such command" error with no indication that a
|
|
38
|
+
plugin was involved.
|
|
37
39
|
"""
|
|
38
40
|
|
|
41
|
+
def __init__(self, *args, **kwargs):
|
|
42
|
+
super().__init__(*args, **kwargs)
|
|
43
|
+
self.core_commands: set[str] = set()
|
|
44
|
+
|
|
45
|
+
def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
46
|
+
"""Format help output with core commands and plugin commands in separate sections."""
|
|
47
|
+
core_rows = []
|
|
48
|
+
plugin_rows = []
|
|
49
|
+
|
|
50
|
+
for subcommand in self.list_commands(ctx):
|
|
51
|
+
cmd = self.get_command(ctx, subcommand)
|
|
52
|
+
if cmd is None or cmd.hidden:
|
|
53
|
+
continue
|
|
54
|
+
target = core_rows if subcommand in self.core_commands else plugin_rows
|
|
55
|
+
target.append((subcommand, cmd))
|
|
56
|
+
|
|
57
|
+
if not core_rows and not plugin_rows:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
all_names = core_rows + plugin_rows
|
|
61
|
+
limit = formatter.width - 6 - max(len(name) for name, _ in all_names)
|
|
62
|
+
|
|
63
|
+
def make_rows(commands):
|
|
64
|
+
return [(name, cmd.get_short_help_str(limit)) for name, cmd in commands]
|
|
65
|
+
|
|
66
|
+
if core_rows:
|
|
67
|
+
with formatter.section("Commands"):
|
|
68
|
+
formatter.write_dl(make_rows(core_rows))
|
|
69
|
+
|
|
70
|
+
if plugin_rows:
|
|
71
|
+
with formatter.section("Plugins"):
|
|
72
|
+
formatter.write_dl(make_rows(plugin_rows))
|
|
73
|
+
|
|
39
74
|
def resolve_command(self, ctx: click.Context, args: list) -> tuple:
|
|
40
75
|
try:
|
|
41
76
|
return super().resolve_command(ctx, args)
|
|
@@ -70,6 +105,7 @@ def _register_commands() -> None:
|
|
|
70
105
|
cli.add_command(config_commands.config)
|
|
71
106
|
cli.add_command(content_commands.content)
|
|
72
107
|
|
|
108
|
+
cli.add_command(execute_commands.execute)
|
|
73
109
|
cli.add_command(graph_commands.graph)
|
|
74
110
|
cli.add_command(integration_commands.integration)
|
|
75
111
|
cli.add_command(manifest_commands.manifest)
|
|
@@ -129,6 +165,7 @@ def _configure_logging(config_data: dict | None) -> LoggingSetup:
|
|
|
129
165
|
# "Exit:" entries for pytest invocations.
|
|
130
166
|
_register_commands()
|
|
131
167
|
CORE_COMMANDS, plugin_manager = _load_plugins()
|
|
168
|
+
cli.core_commands = set(CORE_COMMANDS)
|
|
132
169
|
|
|
133
170
|
# Initialized in main(). None when the module is imported without calling main(),
|
|
134
171
|
# which is the case during test runs.
|
|
@@ -29,12 +29,14 @@ List available content items. Enumerates commands, playbooks and scripts availab
|
|
|
29
29
|
**Options:**
|
|
30
30
|
- `--environment TEXT` - Target environment (default: uses default environment from config)
|
|
31
31
|
- `--type [scripts|playbooks|commands|all]` - Type of content items to list (default: all)
|
|
32
|
+
- `--detail-level [short|extended|full]` - Amount of detail in the output (default: short)
|
|
32
33
|
|
|
33
34
|
**Examples:**
|
|
34
35
|
```
|
|
35
36
|
xsoar-cli content list
|
|
36
37
|
xsoar-cli content list --environment prod
|
|
37
38
|
xsoar-cli content list --type commands
|
|
39
|
+
xsoar-cli content list --type commands --detail-level extended
|
|
38
40
|
xsoar-cli content list --type playbooks --environment dev
|
|
39
41
|
```
|
|
40
42
|
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
import click
|
|
8
8
|
|
|
9
9
|
from xsoar_cli.utilities.config_file import get_xsoar_config, load_config
|
|
10
|
-
from xsoar_cli.utilities.content import filter_content
|
|
10
|
+
from xsoar_cli.utilities.content import DETAIL_LEVELS, filter_content
|
|
11
11
|
from xsoar_cli.utilities.download_content_handlers import HANDLERS, resolve_output_path
|
|
12
12
|
from xsoar_cli.utilities.validators import validate_xsoar_connectivity
|
|
13
13
|
|
|
@@ -55,19 +55,25 @@ def get_detached(ctx: click.Context, environment: str | None, content_type: str)
|
|
|
55
55
|
show_default=True,
|
|
56
56
|
help="Type of content items to list.",
|
|
57
57
|
)
|
|
58
|
-
@click.option(
|
|
59
|
-
|
|
58
|
+
@click.option(
|
|
59
|
+
"--detail-level",
|
|
60
|
+
type=click.Choice(DETAIL_LEVELS, case_sensitive=False),
|
|
61
|
+
default="short",
|
|
62
|
+
show_default=True,
|
|
63
|
+
help="Amount of detail to include in the output.",
|
|
64
|
+
)
|
|
60
65
|
@click.pass_context
|
|
61
66
|
@load_config
|
|
62
67
|
@validate_xsoar_connectivity
|
|
63
|
-
def list_content(ctx: click.Context, environment: str | None, content_type: str,
|
|
68
|
+
def list_content(ctx: click.Context, environment: str | None, content_type: str, detail_level: str) -> None:
|
|
64
69
|
"""
|
|
65
|
-
List
|
|
66
|
-
|
|
70
|
+
List available content items. Enumerates commands, playbooks and scripts
|
|
71
|
+
available on the server, primarily to facilitate better AI generated
|
|
72
|
+
playbooks."""
|
|
67
73
|
config = get_xsoar_config(ctx)
|
|
68
74
|
xsoar_client: Client = config.get_client(environment)
|
|
69
75
|
json_blob = xsoar_client.content.list(content_type)
|
|
70
|
-
if
|
|
76
|
+
if detail_level == "full":
|
|
71
77
|
click.echo(json.dumps(json_blob, indent=4))
|
|
72
78
|
ctx.exit(0)
|
|
73
79
|
|
|
@@ -75,7 +81,7 @@ def list_content(ctx: click.Context, environment: str | None, content_type: str,
|
|
|
75
81
|
if isinstance(json_blob, list):
|
|
76
82
|
json_blob = {content_type: json_blob}
|
|
77
83
|
|
|
78
|
-
filtered = filter_content(json_blob,
|
|
84
|
+
filtered = filter_content(json_blob, detail_level=detail_level)
|
|
79
85
|
click.echo(json.dumps(filtered, indent=4))
|
|
80
86
|
|
|
81
87
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Execute
|
|
2
|
+
|
|
3
|
+
Execute scripts, integration commands, and playbooks against an XSOAR investigation.
|
|
4
|
+
|
|
5
|
+
By default, execution happens in the user's playground. Pass `--case-id` to run against a specific case instead.
|
|
6
|
+
|
|
7
|
+
## Command
|
|
8
|
+
|
|
9
|
+
Execute an automation script or integration command. There is no difference in how scripts and integration commands are invoked.
|
|
10
|
+
|
|
11
|
+
Use the bare command or script name, the same way you would reference it in a playbook. A leading `!` is accepted but optional.
|
|
12
|
+
|
|
13
|
+
Arguments are supplied as space-separated `key=value` pairs. Values containing whitespace are quoted automatically. A malformed argument (one without a `=`) causes the command to exit with an error.
|
|
14
|
+
|
|
15
|
+
**Shell note:** interactive shells (zsh, bash) treat characters like `!` and `|` specially. Prefer omitting the `!`, and wrap argument values in single quotes when they contain shell metacharacters, for example `query='index=zscaler | head 1'`.
|
|
16
|
+
|
|
17
|
+
**Syntax:** `xsoar-cli execute command [OPTIONS] NAME [ARGS]...`
|
|
18
|
+
|
|
19
|
+
**Options:**
|
|
20
|
+
- `--environment TEXT` - Target environment (default: uses default environment from config)
|
|
21
|
+
- `--case-id INTEGER` - Case ID to execute against (default: the user's playground)
|
|
22
|
+
- `--mode [sync|async]` - Wait for results (`sync`) or submit and return the created entry (`async`) (default: "sync")
|
|
23
|
+
- `--timeout INTEGER` - Seconds to wait for results in sync mode before reporting the command is still running (default: 30)
|
|
24
|
+
|
|
25
|
+
**Arguments:**
|
|
26
|
+
- `NAME` - The script or integration command to run
|
|
27
|
+
- `ARGS` - Zero or more `key=value` argument pairs
|
|
28
|
+
|
|
29
|
+
**Output:**
|
|
30
|
+
|
|
31
|
+
On success, prints a completion line with the entry count, then for each resulting entry a short `Entry <id>:` header followed by two links: a War Room link (to view the entry in context) and an artifact viewer link (which offers a direct download of the entry content in XSOAR). The entry contents are not printed. If a command produces an error entry, the error contents are printed alongside its links and the command exits non-zero.
|
|
32
|
+
|
|
33
|
+
In sync mode, the command submits the request and then polls for the resulting War Room entries. If no results appear within `--timeout` seconds, the command is reported as still running with a War Room link to the submitted entry, and the exit code stays zero. A failed submission surfaces immediately as an error with a non-zero exit code.
|
|
34
|
+
|
|
35
|
+
**Examples:**
|
|
36
|
+
```
|
|
37
|
+
xsoar-cli execute command MyScript arg1=val1 arg2=val2
|
|
38
|
+
xsoar-cli execute command whois query=example.com --case-id 12345
|
|
39
|
+
xsoar-cli execute command splunk-search query='index=zscaler | head 1'
|
|
40
|
+
xsoar-cli execute command MyScript arg1=val1 --mode async
|
|
41
|
+
xsoar-cli execute command LongRunningScript --timeout 60
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Playbook
|
|
45
|
+
|
|
46
|
+
Start a playbook in the playground or a specific case. The playbook name is resolved to its ID before starting, so the display name works even for custom playbooks whose ID is a UUID.
|
|
47
|
+
|
|
48
|
+
This is fire-and-forget: the playbook is started via the `setPlaybook` command and the CLI returns without waiting for the playbook to finish. Follow up in the XSOAR War Room to see its progress.
|
|
49
|
+
|
|
50
|
+
**Syntax:** `xsoar-cli execute playbook [OPTIONS] NAME`
|
|
51
|
+
|
|
52
|
+
**Options:**
|
|
53
|
+
- `--environment TEXT` - Target environment (default: uses default environment from config)
|
|
54
|
+
- `--case-id INTEGER` - Case ID to start the playbook in (default: the user's playground)
|
|
55
|
+
|
|
56
|
+
**Arguments:**
|
|
57
|
+
- `NAME` - The playbook to start
|
|
58
|
+
|
|
59
|
+
**Output:**
|
|
60
|
+
|
|
61
|
+
Prints `Started playbook <name> in playground` or `Started playbook <name> in case <id>`. If the playbook name cannot be found, the command exits non-zero with an error.
|
|
62
|
+
|
|
63
|
+
**Examples:**
|
|
64
|
+
```
|
|
65
|
+
xsoar-cli execute playbook "My Playbook"
|
|
66
|
+
xsoar-cli execute playbook "My Playbook" --case-id 12345
|
|
67
|
+
```
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from xsoar_cli.utilities.config_file import get_xsoar_config, load_config
|
|
7
|
+
from xsoar_cli.utilities.validators import validate_xsoar_connectivity
|
|
8
|
+
from xsoar_cli.xsoar_client.execution import DEFAULT_SYNC_TIMEOUT, EXECUTION_MODES
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from xsoar_cli.xsoar_client.client import Client
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# XSOAR entry type identifying an error entry in command output.
|
|
16
|
+
ERROR_ENTRY_TYPE = 4
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_arg_tokens(tokens: tuple[str, ...]) -> dict[str, str]:
|
|
20
|
+
"""Parse variadic key=value tokens into a dictionary.
|
|
21
|
+
|
|
22
|
+
Raises ValueError on any token that does not contain a "=" separator.
|
|
23
|
+
"""
|
|
24
|
+
args: dict[str, str] = {}
|
|
25
|
+
for token in tokens:
|
|
26
|
+
if "=" not in token:
|
|
27
|
+
msg = f"Malformed argument '{token}'. Arguments must be on the form key=value."
|
|
28
|
+
raise ValueError(msg)
|
|
29
|
+
key, value = token.split("=", 1)
|
|
30
|
+
args[key.strip()] = value.strip()
|
|
31
|
+
return args
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def resolve_investigation_id(ctx: click.Context, xsoar_client: "Client", case_id: int | None) -> str:
|
|
35
|
+
"""Return the target investigation ID.
|
|
36
|
+
|
|
37
|
+
Uses the supplied case ID when present, otherwise resolves the user's
|
|
38
|
+
playground investigation ID.
|
|
39
|
+
|
|
40
|
+
Distinguishes two failure modes when resolving the playground:
|
|
41
|
+
|
|
42
|
+
* ApiException means the XSOAR API call itself failed (connectivity, auth,
|
|
43
|
+
server error). The user is pointed at the environment.
|
|
44
|
+
* RuntimeError means the calls succeeded but no single playground could be
|
|
45
|
+
identified. The specific reason is surfaced to the user.
|
|
46
|
+
|
|
47
|
+
Both cases print a message and exit with a non-zero status.
|
|
48
|
+
"""
|
|
49
|
+
# Lazy import for performance reasons
|
|
50
|
+
from demisto_client.demisto_api.rest import ApiException
|
|
51
|
+
|
|
52
|
+
if case_id is not None:
|
|
53
|
+
logger.debug("Using supplied case ID %d as investigation ID", case_id)
|
|
54
|
+
return str(case_id)
|
|
55
|
+
|
|
56
|
+
logger.debug("No case ID supplied, resolving user's playground investigation ID")
|
|
57
|
+
try:
|
|
58
|
+
investigation_id = xsoar_client.execution.resolve_playground_id()
|
|
59
|
+
except ApiException as ex:
|
|
60
|
+
logger.info("Playground lookup failed with API error: %s", ex)
|
|
61
|
+
click.echo(f"Error: failed to query XSOAR for the playground investigation: {ex}")
|
|
62
|
+
ctx.exit(1)
|
|
63
|
+
except RuntimeError as ex:
|
|
64
|
+
logger.info("Could not resolve playground investigation: %s", ex)
|
|
65
|
+
click.echo(f"Error: {ex}")
|
|
66
|
+
ctx.exit(1)
|
|
67
|
+
logger.debug("Resolved playground investigation ID: %s", investigation_id)
|
|
68
|
+
return investigation_id
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_entry_url(server_url: str, view: str, segment: str, entry_id: str) -> str:
|
|
72
|
+
"""Build a direct link to an entry in a given XSOAR view.
|
|
73
|
+
|
|
74
|
+
The URL shape is <server_url>/#/<view>/<segment>/<entry_id>, where view is
|
|
75
|
+
"WarRoom" or "artifactViewer" and segment is the literal "playground" for
|
|
76
|
+
playground executions or the case ID for a case. The server URL's trailing
|
|
77
|
+
slash, if any, is stripped.
|
|
78
|
+
"""
|
|
79
|
+
base = server_url.rstrip("/")
|
|
80
|
+
return f"{base}/#/{view}/{segment}/{entry_id}"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _result_entry_lines(entry_id: str, server_url: str, segment: str) -> list[str]:
|
|
84
|
+
"""Build the grouped output lines for a single result entry.
|
|
85
|
+
|
|
86
|
+
Each result entry is shown with a short header and two links: the War Room
|
|
87
|
+
view for context and the artifact viewer for downloading the entry content.
|
|
88
|
+
The short entry number (the part before "@") is used in the header for
|
|
89
|
+
readability; the full id is preserved in the URLs.
|
|
90
|
+
"""
|
|
91
|
+
short_id = entry_id.split("@", 1)[0]
|
|
92
|
+
return [
|
|
93
|
+
f"Entry {short_id}:",
|
|
94
|
+
f" War Room entry: {build_entry_url(server_url, 'WarRoom', segment, entry_id)}",
|
|
95
|
+
f" Artifact viewer: {build_entry_url(server_url, 'artifactViewer', segment, entry_id)}",
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def render_command_output(
|
|
100
|
+
result: dict,
|
|
101
|
+
name: str,
|
|
102
|
+
server_url: str,
|
|
103
|
+
warroom_segment: str,
|
|
104
|
+
) -> tuple[str, bool]:
|
|
105
|
+
"""Render an execute-command result for display.
|
|
106
|
+
|
|
107
|
+
Returns a (text, had_error) tuple. had_error is True when the command
|
|
108
|
+
returned one or more error entries, so the caller can exit non-zero.
|
|
109
|
+
|
|
110
|
+
Reports only what is known:
|
|
111
|
+
|
|
112
|
+
* sync success: that the command completed, the entry count, and War Room
|
|
113
|
+
and artifact viewer links per returned entry.
|
|
114
|
+
* sync error: the contents of each error entry and links to it.
|
|
115
|
+
* sync timeout: that no results arrived in time and the command may still
|
|
116
|
+
be running, with a War Room link to the submitted entry.
|
|
117
|
+
* async: that the command was submitted, with the created entry ID and a
|
|
118
|
+
War Room link.
|
|
119
|
+
"""
|
|
120
|
+
if "entries" in result:
|
|
121
|
+
if result.get("timed_out"):
|
|
122
|
+
return _render_timeout_summary(result, server_url, warroom_segment), False
|
|
123
|
+
return _render_sync_summary(result["entries"], name, server_url, warroom_segment)
|
|
124
|
+
return _render_async_summary(result.get("entry", {}), name, server_url, warroom_segment), False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _render_sync_summary(entries: list[dict], name: str, server_url: str, warroom_segment: str) -> tuple[str, bool]:
|
|
128
|
+
"""Render the summary for a synchronous (blocking) command execution.
|
|
129
|
+
|
|
130
|
+
Each result entry is shown grouped with its War Room and artifact viewer
|
|
131
|
+
links, separated by a blank line for readability.
|
|
132
|
+
"""
|
|
133
|
+
error_entries = [entry for entry in entries if entry.get("type") == ERROR_ENTRY_TYPE]
|
|
134
|
+
|
|
135
|
+
if error_entries:
|
|
136
|
+
total = len(entries)
|
|
137
|
+
error_count = len(error_entries)
|
|
138
|
+
total_plural = "entry" if total == 1 else "entries"
|
|
139
|
+
error_plural = "error" if error_count == 1 else "errors"
|
|
140
|
+
lines = [f"Command {name} completed with errors ({total} {total_plural}, {error_count} {error_plural})."]
|
|
141
|
+
for entry in error_entries:
|
|
142
|
+
entry_id = entry.get("id", "")
|
|
143
|
+
if not entry_id:
|
|
144
|
+
continue
|
|
145
|
+
lines.append("")
|
|
146
|
+
contents = entry.get("contents")
|
|
147
|
+
if contents:
|
|
148
|
+
lines.append(str(contents))
|
|
149
|
+
lines.extend(_result_entry_lines(entry_id, server_url, warroom_segment))
|
|
150
|
+
return "\n".join(lines), True
|
|
151
|
+
|
|
152
|
+
count = len(entries)
|
|
153
|
+
plural = "entry" if count == 1 else "entries"
|
|
154
|
+
lines = [f"Command {name} completed ({count} {plural})."]
|
|
155
|
+
for entry in entries:
|
|
156
|
+
entry_id = entry.get("id", "")
|
|
157
|
+
if not entry_id:
|
|
158
|
+
continue
|
|
159
|
+
lines.append("")
|
|
160
|
+
lines.extend(_result_entry_lines(entry_id, server_url, warroom_segment))
|
|
161
|
+
return "\n".join(lines), False
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _render_timeout_summary(result: dict, server_url: str, warroom_segment: str) -> str:
|
|
165
|
+
"""Render the summary when a sync command produced no results before timeout.
|
|
166
|
+
|
|
167
|
+
A timeout is treated as success with a warning: the command was submitted
|
|
168
|
+
successfully (a submit failure surfaces fast as an API error), so it is
|
|
169
|
+
almost certainly still running. The link points at the submitted entry.
|
|
170
|
+
"""
|
|
171
|
+
entry_id = result.get("entry_id", "")
|
|
172
|
+
lines = ["No command results within timeout. It may still be running."]
|
|
173
|
+
if entry_id:
|
|
174
|
+
lines.append(f"War Room entry: {build_entry_url(server_url, 'WarRoom', warroom_segment, entry_id)}")
|
|
175
|
+
return "\n".join(lines)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _render_async_summary(entry: dict, name: str, server_url: str, warroom_segment: str) -> str:
|
|
179
|
+
"""Render the summary for an asynchronous command submission."""
|
|
180
|
+
entry_id = entry.get("id", "")
|
|
181
|
+
lines = [f"Command {name} submitted. Entry ID: {entry_id}"]
|
|
182
|
+
if entry_id:
|
|
183
|
+
lines.append(f"War Room entry: {build_entry_url(server_url, 'WarRoom', warroom_segment, entry_id)}")
|
|
184
|
+
return "\n".join(lines)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@click.group()
|
|
188
|
+
def execute() -> None:
|
|
189
|
+
"""Execute scripts, integration commands, and playbooks"""
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@click.command()
|
|
194
|
+
@click.option("--environment", default=None, help="Default environment set in config file.")
|
|
195
|
+
@click.option("--case-id", type=int, default=None, help="Case ID to execute against. Defaults to the user's playground.")
|
|
196
|
+
@click.option(
|
|
197
|
+
"--mode",
|
|
198
|
+
type=click.Choice(EXECUTION_MODES, case_sensitive=False),
|
|
199
|
+
default="sync",
|
|
200
|
+
show_default=True,
|
|
201
|
+
help="Execute synchronously (wait for and return results) or asynchronously (submit and return the entry).",
|
|
202
|
+
)
|
|
203
|
+
@click.option(
|
|
204
|
+
"--timeout",
|
|
205
|
+
type=int,
|
|
206
|
+
default=DEFAULT_SYNC_TIMEOUT,
|
|
207
|
+
show_default=True,
|
|
208
|
+
help="Seconds to wait for results in sync mode before reporting the command is still running.",
|
|
209
|
+
)
|
|
210
|
+
@click.argument("name", type=str)
|
|
211
|
+
@click.argument("args", nargs=-1, type=str)
|
|
212
|
+
@click.pass_context
|
|
213
|
+
@load_config
|
|
214
|
+
@validate_xsoar_connectivity
|
|
215
|
+
def command( # noqa: PLR0913
|
|
216
|
+
ctx: click.Context,
|
|
217
|
+
environment: str | None,
|
|
218
|
+
case_id: int | None,
|
|
219
|
+
mode: str,
|
|
220
|
+
timeout: int,
|
|
221
|
+
name: str,
|
|
222
|
+
args: tuple[str, ...],
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Execute an automation script or integration command.
|
|
225
|
+
|
|
226
|
+
NAME is the script or integration command to run. There is no difference in
|
|
227
|
+
how scripts and integration commands are invoked. Use the bare name (as you
|
|
228
|
+
would reference it in a playbook). A leading "!" is accepted but optional.
|
|
229
|
+
|
|
230
|
+
Arguments are supplied as space-separated key=value pairs. Values containing
|
|
231
|
+
whitespace are quoted automatically.
|
|
232
|
+
|
|
233
|
+
Shell note: interactive shells treat characters like "!" and "|" specially.
|
|
234
|
+
Prefer omitting the "!" and wrap argument values in single quotes when they
|
|
235
|
+
contain shell metacharacters, for example query='index=foo | head 1'.
|
|
236
|
+
|
|
237
|
+
By default execution happens in the user's playground. Pass --case-id to run
|
|
238
|
+
against a specific case instead.
|
|
239
|
+
|
|
240
|
+
--mode sync waits for the command to finish and returns the resulting War
|
|
241
|
+
Room entries. --mode async submits the command and returns immediately with
|
|
242
|
+
the created entry.
|
|
243
|
+
|
|
244
|
+
In sync mode, --timeout bounds how long to wait for results. If the command
|
|
245
|
+
has not produced results by then, it is reported as still running and a War
|
|
246
|
+
Room link is printed; this is not treated as an error.
|
|
247
|
+
|
|
248
|
+
Usage examples:
|
|
249
|
+
|
|
250
|
+
xsoar-cli execute command MyScript arg1=val1 arg2=val2
|
|
251
|
+
|
|
252
|
+
xsoar-cli execute command whois query=example.com --case-id 12345
|
|
253
|
+
|
|
254
|
+
xsoar-cli execute command splunk-search query='index=zscaler | head 1'
|
|
255
|
+
|
|
256
|
+
xsoar-cli execute command MyScript arg1=val1 --mode async
|
|
257
|
+
"""
|
|
258
|
+
# Lazy import for performance reasons
|
|
259
|
+
from demisto_client.demisto_api.rest import ApiException
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
parsed_args = parse_arg_tokens(args)
|
|
263
|
+
except ValueError as ex:
|
|
264
|
+
click.echo(f"Error: {ex}")
|
|
265
|
+
ctx.exit(1)
|
|
266
|
+
|
|
267
|
+
config = get_xsoar_config(ctx)
|
|
268
|
+
xsoar_client: Client = config.get_client(environment)
|
|
269
|
+
investigation_id = resolve_investigation_id(ctx, xsoar_client, case_id)
|
|
270
|
+
# The War Room URL uses the literal "playground" segment for playground
|
|
271
|
+
# executions, and the case ID when running against a specific case.
|
|
272
|
+
warroom_segment = str(case_id) if case_id is not None else "playground"
|
|
273
|
+
# The leading "!" is optional on input; strip it for display so user-facing
|
|
274
|
+
# messages read the same whether or not the user included it.
|
|
275
|
+
display_name = name[1:] if name.startswith("!") else name
|
|
276
|
+
logger.info("Executing command '%s' (mode=%s) against investigation '%s'", name, mode, investigation_id)
|
|
277
|
+
if mode == "sync":
|
|
278
|
+
click.echo(f"Executing {display_name}, waiting up to {timeout}s for results...", err=True)
|
|
279
|
+
try:
|
|
280
|
+
result = xsoar_client.execution.execute_command(name, parsed_args, investigation_id, mode=mode, timeout=timeout)
|
|
281
|
+
except ApiException as ex:
|
|
282
|
+
logger.info("Command execution failed with API error: %s", ex)
|
|
283
|
+
click.echo(f"Error: command execution failed: {ex}")
|
|
284
|
+
ctx.exit(1)
|
|
285
|
+
output, had_error = render_command_output(result, display_name, xsoar_client.server_url, warroom_segment)
|
|
286
|
+
click.echo(output)
|
|
287
|
+
if had_error:
|
|
288
|
+
ctx.exit(1)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@click.command()
|
|
292
|
+
@click.option("--environment", default=None, help="Default environment set in config file.")
|
|
293
|
+
@click.option("--case-id", type=int, default=None, help="Case ID to execute against. Defaults to the user's playground.")
|
|
294
|
+
@click.argument("name", type=str)
|
|
295
|
+
@click.pass_context
|
|
296
|
+
@load_config
|
|
297
|
+
@validate_xsoar_connectivity
|
|
298
|
+
def playbook(ctx: click.Context, environment: str | None, case_id: int | None, name: str) -> None:
|
|
299
|
+
"""Start a playbook.
|
|
300
|
+
|
|
301
|
+
NAME is the playbook to run.
|
|
302
|
+
|
|
303
|
+
By default the playbook is started in the user's playground. Pass --case-id
|
|
304
|
+
to start it in a specific case instead.
|
|
305
|
+
|
|
306
|
+
This is fire-and-forget: the playbook is started and the command returns
|
|
307
|
+
without waiting for it to finish.
|
|
308
|
+
|
|
309
|
+
Usage examples:
|
|
310
|
+
|
|
311
|
+
xsoar-cli execute playbook "My Playbook"
|
|
312
|
+
|
|
313
|
+
xsoar-cli execute playbook "My Playbook" --case-id 12345
|
|
314
|
+
"""
|
|
315
|
+
# Lazy import for performance reasons
|
|
316
|
+
from demisto_client.demisto_api.rest import ApiException
|
|
317
|
+
|
|
318
|
+
config = get_xsoar_config(ctx)
|
|
319
|
+
xsoar_client: Client = config.get_client(environment)
|
|
320
|
+
investigation_id = resolve_investigation_id(ctx, xsoar_client, case_id)
|
|
321
|
+
target = f"case {case_id}" if case_id is not None else "playground"
|
|
322
|
+
logger.info("Starting playbook '%s' in %s (investigation '%s')", name, target, investigation_id)
|
|
323
|
+
try:
|
|
324
|
+
xsoar_client.execution.execute_playbook(name, investigation_id)
|
|
325
|
+
except ValueError as ex:
|
|
326
|
+
logger.info("Could not start playbook: %s", ex)
|
|
327
|
+
click.echo(f"Error: {ex}", err=True)
|
|
328
|
+
ctx.exit(1)
|
|
329
|
+
except ApiException as ex:
|
|
330
|
+
logger.info("Playbook start failed with API error: %s", ex)
|
|
331
|
+
click.echo(f"Error: failed to start playbook: {ex}", err=True)
|
|
332
|
+
ctx.exit(1)
|
|
333
|
+
click.echo(f"Started playbook {name} in {target}")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
execute.add_command(command)
|
|
337
|
+
execute.add_command(playbook)
|
|
@@ -23,11 +23,13 @@ xsoar-cli manifest generate --environment prod
|
|
|
23
23
|
|
|
24
24
|
Validate manifest JSON syntax and verify all specified content packs are available. Tests connectivity to pack sources and checks local pack metadata for development packs.
|
|
25
25
|
|
|
26
|
+
By default, all packs in the manifest are validated. Use `--only-changed` to limit validation to packs whose version differs from what is currently installed on the server. This is useful in CI pipelines where only a few packs change per PR.
|
|
27
|
+
|
|
26
28
|
**Syntax:** `xsoar-cli manifest validate [OPTIONS] MANIFEST_PATH`
|
|
27
29
|
|
|
28
30
|
**Options:**
|
|
29
31
|
- `--environment TEXT` - Target environment (default: uses default environment from config)
|
|
30
|
-
- `--
|
|
32
|
+
- `--only-changed` - Only validate packs that differ from what is currently installed on the server
|
|
31
33
|
|
|
32
34
|
**Arguments:**
|
|
33
35
|
- `MANIFEST_PATH` - Path to the manifest file to validate
|
|
@@ -35,6 +37,7 @@ Validate manifest JSON syntax and verify all specified content packs are availab
|
|
|
35
37
|
**Examples:**
|
|
36
38
|
```
|
|
37
39
|
xsoar-cli manifest validate ./xsoar_config.json
|
|
40
|
+
xsoar-cli manifest validate --only-changed ./xsoar_config.json
|
|
38
41
|
xsoar-cli manifest validate --environment staging ./xsoar_config.json
|
|
39
42
|
```
|
|
40
43
|
|