xsoar-cli 1.0.3__tar.gz → 1.0.5__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.

Potentially problematic release.


This version of xsoar-cli might be problematic. Click here for more details.

Files changed (93) hide show
  1. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/.github/workflows/pull-request-open.yml +6 -3
  2. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/.github/workflows/release.yml +2 -31
  3. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/PKG-INFO +4 -2
  4. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/pyproject.toml +3 -1
  5. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/__about__.py +1 -1
  6. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/case/commands.py +28 -9
  7. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/config/commands.py +12 -2
  8. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/manifest/commands.py +49 -14
  9. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/pack/commands.py +10 -6
  10. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/playbook/commands.py +4 -3
  11. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/plugins/README.md +23 -23
  12. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/utilities.py +42 -2
  13. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/.gitignore +0 -0
  14. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/CONTRIBUTING.md +0 -0
  15. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/LICENSE.txt +0 -0
  16. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/README.md +0 -0
  17. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/examples/README.md +0 -0
  18. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/examples/advanced_plugin.py +0 -0
  19. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/requirements.txt +0 -0
  20. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/requirements_dev.txt +0 -0
  21. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/__init__.py +0 -0
  22. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/case/README.md +0 -0
  23. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/case/__init__.py +0 -0
  24. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/cli.py +0 -0
  25. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/config/README.md +0 -0
  26. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/config/__init__.py +0 -0
  27. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/graph/README.md +0 -0
  28. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/graph/__init__.py +0 -0
  29. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/graph/commands.py +0 -0
  30. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/manifest/README.md +0 -0
  31. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/manifest/__init__.py +0 -0
  32. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/pack/README.md +0 -0
  33. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/pack/__init__.py +0 -0
  34. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/playbook/README.md +0 -0
  35. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/playbook/__init__.py +0 -0
  36. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/plugins/__init__.py +0 -0
  37. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/plugins/commands.py +0 -0
  38. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/src/xsoar_cli/plugins/manager.py +0 -0
  39. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/__init__.py +0 -0
  40. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/conftest.py +0 -0
  41. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_base.py +0 -0
  42. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_case.py +0 -0
  43. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_config.py +0 -0
  44. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Download/playbook-empty.yml +0 -0
  45. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonPlaybooks/.pack-ignore +0 -0
  46. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonPlaybooks/.secrets-ignore +0 -0
  47. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonPlaybooks/Author_image.png +0 -0
  48. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonPlaybooks/Playbooks/GenericPlaybook.yml +0 -0
  49. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonPlaybooks/README.md +0 -0
  50. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonPlaybooks/pack_metadata.json +0 -0
  51. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonScripts/.pack-ignore +0 -0
  52. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonScripts/.secrets-ignore +0 -0
  53. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonScripts/Author_image.png +0 -0
  54. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonScripts/README.md +0 -0
  55. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.py +0 -0
  56. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.yml +0 -0
  57. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonScripts/Scripts/GenericScript/README.md +0 -0
  58. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_CommonScripts/pack_metadata.json +0 -0
  59. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/.pack-ignore +0 -0
  60. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/.secrets-ignore +0 -0
  61. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Author_image.png +0 -0
  62. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Playbooks/EDR_InitialTriage.yml +0 -0
  63. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/README.md +0 -0
  64. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.py +0 -0
  65. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.yml +0 -0
  66. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/README.md +0 -0
  67. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.py +0 -0
  68. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.yml +0 -0
  69. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/EDR_Triage/README.md +0 -0
  70. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.py +0 -0
  71. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.yml +0 -0
  72. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/LegacyItem/README.md +0 -0
  73. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/Scripts/LegacyItem/test_data/basescript-dummy.json +0 -0
  74. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_EDR/pack_metadata.json +0 -0
  75. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_Layouts/.pack-ignore +0 -0
  76. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_Layouts/.secrets-ignore +0 -0
  77. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_Layouts/Author_image.png +0 -0
  78. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_Layouts/Layouts/layoutscontainer-GenericLayout.json +0 -0
  79. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_Layouts/README.md +0 -0
  80. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/MyOrg_Layouts/pack_metadata.json +0 -0
  81. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/Packs/Not_applicable/Playbooks/Empty.yml +0 -0
  82. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/manifest_base.json +0 -0
  83. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/manifest_invalid.json +0 -0
  84. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/manifest_with_pack_not_on_server.json +0 -0
  85. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/server_base_response.json +0 -0
  86. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/server_base_response_missing_one_pack.json +0 -0
  87. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/server_base_response_with_updates.json +0 -0
  88. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_data/server_base_response_with_updates_and_one_extra.json +0 -0
  89. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_graph.py +0 -0
  90. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_manifest.py +0 -0
  91. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_pack.py +0 -0
  92. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_playbook.py +0 -0
  93. {xsoar_cli-1.0.3 → xsoar_cli-1.0.5}/tests/test_plugins.py +0 -0
@@ -5,14 +5,18 @@ on:
5
5
  jobs:
6
6
  test:
7
7
  runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ python-version: ["3.10", "3.11", "3.12"]
8
11
  steps:
9
12
  - name: Checkout code
10
13
  uses: actions/checkout@v4
11
14
 
12
- - name: Setup Python 3.12
15
+ - name: Set up Python ${{ matrix.python-version }}
13
16
  uses: actions/setup-python@v5
14
17
  with:
15
- python-version: "3.12"
18
+ python-version: ${{ matrix.python-version }}
19
+ cache: "pip"
16
20
 
17
21
  - name: Create and activate virtual environment
18
22
  run: |
@@ -21,7 +25,6 @@ jobs:
21
25
 
22
26
  - name: Install requirements
23
27
  run: |
24
- pip install -r requirements.txt
25
28
  pip install -r requirements_dev.txt
26
29
 
27
30
  - name: Install xsoar-cli
@@ -1,12 +1,8 @@
1
1
  name: release
2
2
 
3
3
  on:
4
- push:
5
- tags:
6
- - "[0-9]+.[0-9]+.[0-9]+"
7
- - "[0-9]+.[0-9]+.[0-9]+a[0-9]+"
8
- - "[0-9]+.[0-9]+.[0-9]+b[0-9]+"
9
- - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
4
+ release:
5
+ types: [published]
10
6
 
11
7
  env:
12
8
  PACKAGE_NAME: "xsoar-cli"
@@ -117,28 +113,3 @@ jobs:
117
113
 
118
114
  - name: Publish distribution to PyPI
119
115
  uses: pypa/gh-action-pypi-publish@release/v1
120
-
121
- github_release:
122
- name: Create GitHub Release
123
- needs: [setup_and_build, details]
124
- runs-on: ubuntu-latest
125
- permissions:
126
- contents: write
127
- steps:
128
- - name: Checkout Code
129
- uses: actions/checkout@v3
130
- with:
131
- fetch-depth: 0
132
-
133
- - name: Download artifacts
134
- uses: actions/download-artifact@v4
135
- with:
136
- name: dist
137
- path: dist/
138
-
139
- - name: Create GitHub Release
140
- id: create_release
141
- env:
142
- GH_TOKEN: ${{ github.token }}
143
- run: |
144
- gh release create ${{ needs.details.outputs.tag_name }} dist/* --title ${{ needs.details.outputs.tag_name }} --generate-notes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xsoar-cli
3
- Version: 1.0.3
3
+ Version: 1.0.5
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
@@ -9,10 +9,12 @@ License-Expression: MIT
9
9
  License-File: LICENSE.txt
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
12
14
  Classifier: Programming Language :: Python :: 3.12
13
15
  Classifier: Programming Language :: Python :: Implementation :: CPython
14
16
  Classifier: Programming Language :: Python :: Implementation :: PyPy
15
- Requires-Python: >=3.12
17
+ Requires-Python: <3.13,>=3.10
16
18
  Requires-Dist: click==8.1.8
17
19
  Requires-Dist: pyyaml>=6.0.2
18
20
  Requires-Dist: xsoar-client>=1.0.0
@@ -7,13 +7,15 @@ name = "xsoar-cli"
7
7
  dynamic = ["version"]
8
8
  description = ''
9
9
  readme = "README.md"
10
- requires-python = ">=3.12"
10
+ requires-python = ">=3.10, <3.13"
11
11
  license = "MIT"
12
12
  keywords = []
13
13
  authors = [{ name = "Torbjørn Lium", email = "torben@lium.org" }]
14
14
  classifiers = [
15
15
  "Development Status :: 4 - Beta",
16
16
  "Programming Language :: Python",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
17
19
  "Programming Language :: Python :: 3.12",
18
20
  "Programming Language :: Python :: Implementation :: CPython",
19
21
  "Programming Language :: Python :: Implementation :: PyPy",
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Torbjørn Lium <torben@lium.org>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "1.0.3"
4
+ __version__ = "1.0.5"
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
3
3
 
4
4
  import click
5
5
 
6
- from xsoar_cli.utilities import load_config, validate_environments
6
+ from xsoar_cli.utilities import load_config, parse_string_to_dict, validate_environments
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from xsoar_client.xsoar_client import Client
@@ -15,12 +15,14 @@ def case() -> None:
15
15
 
16
16
 
17
17
  @click.argument("casenumber", type=int)
18
- @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
18
+ @click.option("--environment", default=None, help="Default environment set in config file.")
19
19
  @click.command(help="Get basic information about a single case in XSOAR")
20
20
  @click.pass_context
21
21
  @load_config
22
- def get(ctx: click.Context, casenumber: int, environment: str) -> None:
23
- xsoar_client: Client = ctx.obj["server_envs"][environment]
22
+ def get(ctx: click.Context, casenumber: int, environment: str | None) -> None:
23
+ if not environment:
24
+ environment = ctx.obj["default_environment"]
25
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
24
26
  response = xsoar_client.get_case(casenumber)
25
27
  if response["total"] == 0 and not response["data"]:
26
28
  click.echo(f"Cannot find case ID {casenumber}")
@@ -40,7 +42,7 @@ def clone(ctx: click.Context, casenumber: int, source: str, dest: str) -> None:
40
42
  if not valid_envs:
41
43
  click.echo(f"Error: cannot find environments {source} and/or {dest} in config")
42
44
  ctx.exit(1)
43
- xsoar_source_client: Client = ctx.obj["server_envs"][source]
45
+ xsoar_source_client: Client = ctx.obj["server_envs"][source]["xsoar_client"]
44
46
  results = xsoar_source_client.get_case(casenumber)
45
47
  data = results["data"][0]
46
48
  # Dbot mirror info is irrelevant. This will be added again if applicable by XSOAR after ticket creation in dev.
@@ -57,21 +59,37 @@ def clone(ctx: click.Context, casenumber: int, source: str, dest: str) -> None:
57
59
  # Ensure that playbooks run immediately when the case is created
58
60
  data["createInvestigation"] = True
59
61
 
60
- xsoar_dest_client: Client = ctx.obj["server_envs"][dest]
62
+ xsoar_dest_client: Client = ctx.obj["server_envs"][dest]["xsoar_client"]
61
63
  case_data = xsoar_dest_client.create_case(data=data)
62
64
  click.echo(json.dumps(case_data, indent=4))
63
65
 
64
66
 
65
- @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
67
+ @click.option("--environment", default=None, help="Default environment set in config file.")
66
68
  @click.option("--casetype", default="", show_default=True, help="Create case of specified type. Default type set in config file.")
69
+ @click.option(
70
+ "--custom-fields",
71
+ default=None,
72
+ help='Additional fields on the form "myfield=my_value,anotherfield=another value". Use machine name for field names, e.g mycustomfieldname.',
73
+ )
74
+ @click.option("--custom-fields-delimiter", default=",", help='Delimiter when specifying additional fields. Default is ","')
67
75
  @click.argument("details", type=str, default="Placeholder case details")
68
76
  @click.argument("name", type=str, default="Test case created from xsoar-cli")
69
77
  @click.command()
70
78
  @click.pass_context
71
79
  @load_config
72
- def create(ctx: click.Context, environment: str, casetype: str, name: str, details: str) -> None:
80
+ def create( # noqa: PLR0913
81
+ ctx: click.Context,
82
+ environment: str | None,
83
+ casetype: str,
84
+ name: str,
85
+ custom_fields: str | None,
86
+ custom_fields_delimiter: str | None,
87
+ details: str,
88
+ ) -> None:
73
89
  """Creates a new case in XSOAR. If invalid case type is specified as a command option, XSOAR will default to using Unclassified."""
74
- xsoar_client: Client = ctx.obj["server_envs"][environment]
90
+ if not environment:
91
+ environment = ctx.obj["default_environment"]
92
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
75
93
  if not casetype:
76
94
  casetype = ctx.obj["default_new_case_type"]
77
95
  data = {
@@ -79,6 +97,7 @@ def create(ctx: click.Context, environment: str, casetype: str, name: str, detai
79
97
  "name": name,
80
98
  "type": casetype,
81
99
  "details": details,
100
+ "CustomFields": parse_string_to_dict(custom_fields, custom_fields_delimiter),
82
101
  }
83
102
  case_data = xsoar_client.create_case(data=data)
84
103
  case_id = case_data["id"]
@@ -8,7 +8,13 @@ if TYPE_CHECKING:
8
8
 
9
9
  import contextlib
10
10
 
11
- from xsoar_cli.utilities import get_config_file_contents, get_config_file_path, get_config_file_template_contents, load_config
11
+ from xsoar_cli.utilities import (
12
+ fail_if_no_artifacts_provider,
13
+ get_config_file_contents,
14
+ get_config_file_path,
15
+ get_config_file_template_contents,
16
+ load_config,
17
+ )
12
18
 
13
19
 
14
20
  @click.group(help="Create/validate etc")
@@ -35,6 +41,7 @@ def show(ctx: click.Context, masked: bool) -> None:
35
41
  @click.option("--only-test-environment", default=None, show_default=True, help="Environment as defined in config file")
36
42
  @click.pass_context
37
43
  @load_config
44
+ @fail_if_no_artifacts_provider
38
45
  def validate(ctx: click.Context, only_test_environment: str) -> None:
39
46
  """Validates that the configuration file is JSON and tests connectivity for each XSOAR Client environment defined."""
40
47
  return_code = 0
@@ -44,7 +51,7 @@ def validate(ctx: click.Context, only_test_environment: str) -> None:
44
51
  # what the user specified in option
45
52
  continue
46
53
  click.echo(f'Testing "{server_env}" environment...', nl=False)
47
- xsoar_client: Client = ctx.obj["server_envs"][server_env]
54
+ xsoar_client: Client = ctx.obj["server_envs"][server_env]["xsoar_client"]
48
55
  try:
49
56
  xsoar_client.test_connectivity()
50
57
  except ConnectionError as ex:
@@ -53,6 +60,9 @@ def validate(ctx: click.Context, only_test_environment: str) -> None:
53
60
  return_code = 1
54
61
  continue
55
62
  click.echo("OK")
63
+ if ctx.obj["default_environment"] not in ctx.obj["server_envs"]:
64
+ click.echo(f'Error: default environment "{ctx.obj["default_environment"]}" not found in server config.')
65
+ return_code = 1
56
66
  ctx.exit(return_code)
57
67
 
58
68
 
@@ -39,14 +39,43 @@ def manifest() -> None:
39
39
  """Various commands to interact/update/deploy content packs defined in the xsoar_config.json manifest."""
40
40
 
41
41
 
42
- @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
42
+ @click.option("--environment", default=None, help="Default environment set in config file.")
43
+ @click.argument("manifest_path", type=str)
44
+ @click.command()
45
+ @click.pass_context
46
+ @load_config
47
+ def generate(ctx: click.Context, environment: str | None, manifest_path: str) -> None:
48
+ """Generate a new xsoar_config.json manifest from installed content packs.
49
+
50
+ This command assumes that you do not have any custom content packs uploaded to XSOAR.
51
+ All packs will be added as "marketplace_packs" in the manifest.
52
+ """
53
+ if not environment:
54
+ environment = ctx.obj["default_environment"]
55
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
56
+ installed_packs = xsoar_client.get_installed_packs()
57
+ manifest_data = {
58
+ "marketplace_packs": [],
59
+ }
60
+ for item in installed_packs:
61
+ tmpobj = {
62
+ "id": item["id"],
63
+ "version": item["currentVersion"],
64
+ }
65
+ manifest_data["marketplace_packs"].append(tmpobj)
66
+ write_manifest(manifest_path, manifest_data)
67
+
68
+
69
+ @click.option("--environment", default=None, help="Default environment set in config file.")
43
70
  @click.argument("manifest", type=str)
44
71
  @click.command()
45
72
  @click.pass_context
46
73
  @load_config
47
- def update(ctx: click.Context, environment: str, manifest: str) -> None:
74
+ def update(ctx: click.Context, environment: str | None, manifest: str) -> None:
48
75
  """Update manifest on disk with latest available content pack versions."""
49
- xsoar_client: Client = ctx.obj["server_envs"][environment]
76
+ if not environment:
77
+ environment = ctx.obj["default_environment"]
78
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
50
79
  manifest_data = load_manifest(manifest)
51
80
  click.echo("Fetching outdated packs from XSOAR server. This may take a minute...", nl=False)
52
81
  results = xsoar_client.get_outdated_packs()
@@ -81,15 +110,17 @@ def update(ctx: click.Context, environment: str, manifest: str) -> None:
81
110
  write_manifest(manifest, manifest_data)
82
111
 
83
112
 
84
- @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
113
+ @click.option("--environment", default=None, help="Default environment set in config file.")
85
114
  @click.argument("manifest", type=str)
86
115
  @click.command()
87
116
  @click.pass_context
88
117
  @load_config
89
- def validate(ctx: click.Context, environment: str, manifest: str) -> None:
118
+ def validate(ctx: click.Context, environment: str | None, manifest: str) -> None:
90
119
  """Validate manifest JSON and all pack availability. Validates upstream pack availability by doing HTTP CONNECT.
91
120
  Custom pack availability is implementation dependant."""
92
- xsoar_client: Client = ctx.obj["server_envs"][environment]
121
+ if not environment:
122
+ environment = ctx.obj["default_environment"]
123
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
93
124
  manifest_data = load_manifest(manifest)
94
125
  click.echo("Manifest is valid JSON")
95
126
  keys = ["custom_packs", "marketplace_packs"]
@@ -121,15 +152,17 @@ def validate(ctx: click.Context, environment: str, manifest: str) -> None:
121
152
  click.echo("Manifest is valid JSON and all packs are reachable.")
122
153
 
123
154
 
124
- @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
155
+ @click.option("--environment", default=None, help="Default environment set in config file.")
125
156
  @click.argument("manifest", type=str)
126
157
  @click.command()
127
158
  @click.pass_context
128
159
  @load_config
129
- def diff(ctx: click.Context, manifest: str, environment: str) -> None:
160
+ def diff(ctx: click.Context, manifest: str, environment: str | None) -> None:
130
161
  """Prints out the differences (if any) between what is defined in the xsoar_config.json manifest and what is actually
131
162
  installed on the XSOAR server."""
132
- xsoar_client: Client = ctx.obj["server_envs"][environment]
163
+ if not environment:
164
+ environment = ctx.obj["default_environment"]
165
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
133
166
  manifest_data = load_manifest(manifest)
134
167
  installed_packs = xsoar_client.get_installed_packs()
135
168
  all_good = True
@@ -147,14 +180,14 @@ def diff(ctx: click.Context, manifest: str, environment: str) -> None:
147
180
  click.echo("All packs up to date.")
148
181
 
149
182
 
150
- @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
183
+ @click.option("--environment", default=None, help="Default environment set in config file.")
151
184
  @click.option("--verbose", is_flag=True, default=False)
152
185
  @click.option("--yes", is_flag=True, default=False)
153
186
  @click.command()
154
187
  @click.argument("manifest", type=str)
155
188
  @click.pass_context
156
189
  @load_config
157
- def deploy(ctx: click.Context, environment: str, manifest: str, verbose: bool, yes: bool) -> None: # noqa: FBT001
190
+ def deploy(ctx: click.Context, environment: str | None, manifest: str, verbose: bool, yes: bool) -> None: # noqa: FBT001
158
191
  """
159
192
  Deploys content packs to the XSOAR server as defined in the xsoar_config.json manifest.
160
193
  The PATH argument expects the full or relative path to xsoar_config.json
@@ -169,8 +202,9 @@ def deploy(ctx: click.Context, environment: str, manifest: str, verbose: bool, y
169
202
  )
170
203
  if not should_continue:
171
204
  ctx.exit()
172
-
173
- xsoar_client: Client = ctx.obj["server_envs"][environment]
205
+ if not environment:
206
+ environment = ctx.obj["default_environment"]
207
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
174
208
  manifest_data = load_manifest(manifest)
175
209
  click.echo("Fetching installed packs...", err=True)
176
210
  installed_packs = xsoar_client.get_installed_packs()
@@ -191,10 +225,11 @@ def deploy(ctx: click.Context, environment: str, manifest: str, verbose: bool, y
191
225
  # Print message that install is skipped
192
226
 
193
227
  if none_installed:
194
- click.echo("No packs to install. XSOAR server is up to date with manifest.")
228
+ click.echo("No packs to install. All packs and versions in manifest is already installed on XSOAR server.")
195
229
 
196
230
 
197
231
  manifest.add_command(deploy)
198
232
  manifest.add_command(diff)
199
233
  manifest.add_command(update)
200
234
  manifest.add_command(validate)
235
+ manifest.add_command(generate)
@@ -15,14 +15,16 @@ def pack(ctx: click.Context) -> None:
15
15
  """Various content pack related commands."""
16
16
 
17
17
 
18
- @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
18
+ @click.option("--environment", default=None, help="Default environment set in config file.")
19
19
  @click.command()
20
20
  @click.argument("pack_id", type=str)
21
21
  @click.pass_context
22
22
  @load_config
23
- def delete(ctx: click.Context, environment: str, pack_id: str) -> None:
23
+ def delete(ctx: click.Context, environment: str | None, pack_id: str) -> None:
24
24
  """Deletes a content pack from the XSOAR server."""
25
- xsoar_client: Client = ctx.obj["server_envs"][environment]
25
+ if not environment:
26
+ environment = ctx.obj["default_environment"]
27
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
26
28
  if not xsoar_client.is_installed(pack_id=pack_id):
27
29
  click.echo(f"Pack ID {pack_id} is not installed. Cannot delete.")
28
30
  sys.exit(1)
@@ -30,13 +32,15 @@ def delete(ctx: click.Context, environment: str, pack_id: str) -> None:
30
32
  click.echo(f"Deleted pack {pack_id} from XSOAR {environment}")
31
33
 
32
34
 
33
- @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
35
+ @click.option("--environment", default=None, help="Default environment set in config file.")
34
36
  @click.command()
35
37
  @click.pass_context
36
38
  @load_config
37
- def get_outdated(ctx: click.Context, environment: str) -> None:
39
+ def get_outdated(ctx: click.Context, environment: str | None) -> None:
38
40
  """Prints out a list of outdated content packs."""
39
- xsoar_client: Client = ctx.obj["server_envs"][environment]
41
+ if not environment:
42
+ environment = ctx.obj["default_environment"]
43
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
40
44
  click.echo("Fetching outdated packs. This may take a little while...", err=True)
41
45
  outdated_packs = xsoar_client.get_outdated_packs()
42
46
  if not outdated_packs:
@@ -19,12 +19,12 @@ def playbook(ctx: click.Context) -> None:
19
19
  """Download/attach/detach playbooks"""
20
20
 
21
21
 
22
- @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
22
+ @click.option("--environment", default=None, help="Default environment set in config file.")
23
23
  @click.command()
24
24
  @click.argument("name", type=str)
25
25
  @click.pass_context
26
26
  @load_config
27
- def download(ctx: click.Context, environment: str, name: str) -> None:
27
+ def download(ctx: click.Context, environment: str | None, name: str) -> None:
28
28
  """Download and reattach playbook.
29
29
 
30
30
  We try to detect output path to $(cwd)/Packs/<Pack ID>/Playbooks/<name>.yml
@@ -32,7 +32,8 @@ def download(ctx: click.Context, environment: str, name: str) -> None:
32
32
  then demisto-sdk format --assume-yes --no-validate --no-graph is done on the downloaded playbook before the item
33
33
  is re-attached in XSOAR.
34
34
  """
35
-
35
+ if not environment:
36
+ environment = ctx.obj["default_environment"]
36
37
  xsoar_client: Client = ctx.obj["server_envs"][environment]
37
38
  # Maybe we should search for the playbook before attempting download in
38
39
  # case user specifies a cutsom playbook and not a system playbook
@@ -40,20 +40,20 @@ class MyPlugin(XSOARPlugin):
40
40
  @property
41
41
  def name(self) -> str:
42
42
  return "myplugin"
43
-
44
- @property
43
+
44
+ @property
45
45
  def version(self) -> str:
46
46
  return "1.0.0"
47
-
47
+
48
48
  @property
49
49
  def description(self) -> str:
50
50
  return "My custom plugin"
51
-
51
+
52
52
  def get_command(self) -> click.Command:
53
53
  @click.command(help="My custom command")
54
54
  def mycommand():
55
55
  click.echo("Hello from my plugin!")
56
-
56
+
57
57
  return mycommand
58
58
  ```
59
59
 
@@ -75,23 +75,23 @@ class MyCustomPlugin(XSOARPlugin):
75
75
  @property
76
76
  def name(self) -> str:
77
77
  return "mycustom"
78
-
78
+
79
79
  @property
80
80
  def version(self) -> str:
81
81
  return "1.0.0"
82
-
82
+
83
83
  @property
84
84
  def description(self) -> str:
85
85
  return "A custom plugin with multiple commands"
86
-
86
+
87
87
  def get_command(self) -> click.Command:
88
88
  """Return the main command group for this plugin."""
89
-
89
+
90
90
  @click.group(help="My custom commands")
91
91
  def mycustom():
92
92
  """Main command group for my custom plugin."""
93
93
  pass
94
-
94
+
95
95
  @click.command(help="Greet someone")
96
96
  @click.option("--name", default="World", help="Name to greet")
97
97
  @click.option("--times", default=1, help="Number of times to greet")
@@ -99,7 +99,7 @@ class MyCustomPlugin(XSOARPlugin):
99
99
  """Greet someone multiple times."""
100
100
  for i in range(times):
101
101
  click.echo(f"Hello, {name}!")
102
-
102
+
103
103
  @click.command(help="Show current status")
104
104
  @click.option("--verbose", "-v", is_flag=True, help="Verbose output")
105
105
  def status(verbose: bool):
@@ -108,7 +108,7 @@ class MyCustomPlugin(XSOARPlugin):
108
108
  if verbose:
109
109
  click.echo(f"Description: {self.description}")
110
110
  click.echo("Status: Active")
111
-
111
+
112
112
  @click.command(help="Process a file")
113
113
  @click.argument("filename", type=click.Path(exists=True))
114
114
  @click.option("--output", "-o", help="Output file")
@@ -118,18 +118,18 @@ class MyCustomPlugin(XSOARPlugin):
118
118
  if output:
119
119
  click.echo(f"Output will be saved to: {output}")
120
120
  # Your processing logic here
121
-
121
+
122
122
  # Add commands to the group
123
123
  mycustom.add_command(greet)
124
124
  mycustom.add_command(status)
125
125
  mycustom.add_command(process)
126
-
126
+
127
127
  return mycustom
128
-
128
+
129
129
  def initialize(self):
130
130
  """Initialize the plugin."""
131
131
  click.echo("My custom plugin initialized!")
132
-
132
+
133
133
  def cleanup(self):
134
134
  """Cleanup when the plugin is unloaded."""
135
135
  pass
@@ -176,7 +176,7 @@ from xsoar_cli.utilities import load_config
176
176
  @load_config
177
177
  def my_command(ctx: click.Context):
178
178
  # Access XSOAR client
179
- xsoar_client = ctx.obj["server_envs"]["dev"]
179
+ xsoar_client = ctx.obj["server_envs"]["dev"]["xsoar_client"]
180
180
  # Use the client...
181
181
  ```
182
182
 
@@ -284,11 +284,11 @@ class MyPlugin(XSOARPlugin):
284
284
  @click.group()
285
285
  def myplugin():
286
286
  pass
287
-
287
+
288
288
  @click.command()
289
289
  def case(): # ✅ Namespaced as 'myplugin case'
290
290
  click.echo("My case command")
291
-
291
+
292
292
  myplugin.add_command(case)
293
293
  return myplugin
294
294
  ```
@@ -297,7 +297,7 @@ class MyPlugin(XSOARPlugin):
297
297
 
298
298
  These command names are reserved by the core CLI:
299
299
  - `case` - Case/incident management
300
- - `config` - Configuration management
300
+ - `config` - Configuration management
301
301
  - `graph` - Dependency graphs
302
302
  - `manifest` - Manifest operations
303
303
  - `pack` - Content pack operations
@@ -330,7 +330,7 @@ def get_command(self) -> click.Command:
330
330
  except Exception as e:
331
331
  click.echo(f"Error: {e}", err=True)
332
332
  raise click.Abort()
333
-
333
+
334
334
  return mycommand
335
335
  ```
336
336
 
@@ -346,7 +346,7 @@ def my_command(ctx: click.Context):
346
346
  # Access configuration
347
347
  config = ctx.obj
348
348
  # Access XSOAR clients
349
- dev_client = ctx.obj["server_envs"]["dev"]
349
+ dev_client = ctx.obj["server_envs"]["dev"]["xsoar_client"]
350
350
  ```
351
351
 
352
352
  ### 5. Logging
@@ -430,4 +430,4 @@ except ImportError:
430
430
 
431
431
  You can share plugins by simply sharing the Python file. Users can place it in their plugins directory and it will be automatically discovered.
432
432
 
433
- For more complex plugins, consider packaging them as proper Python packages that install the plugin file automatically.
433
+ For more complex plugins, consider packaging them as proper Python packages that install the plugin file automatically.
@@ -1,3 +1,4 @@
1
+ import contextlib
1
2
  import json
2
3
  from collections.abc import Callable
3
4
  from functools import update_wrapper
@@ -7,6 +8,16 @@ import click
7
8
  from xsoar_client.xsoar_client import Client
8
9
 
9
10
 
11
+ def parse_string_to_dict(input_string: str | None, delimiter: str) -> dict:
12
+ if not input_string:
13
+ return {}
14
+ # Parse a string into a python dictionary
15
+ pairs = [pair.split("=", 1) for pair in input_string.split(delimiter)]
16
+ # Filter pairs that have exactly 2 parts (key and value) after splitting by "="
17
+ valid_pairs = [pair for pair in pairs if len(pair) == 2]
18
+ return {key.strip(): value.strip() for key, value in valid_pairs}
19
+
20
+
10
21
  def get_config_file_template_contents() -> dict:
11
22
  return {
12
23
  "default_environment": "dev",
@@ -66,7 +77,11 @@ def load_config(f: Callable) -> Callable:
66
77
  ctx.exit(1)
67
78
  config = get_config_file_contents(config_file_path)
68
79
  parse_config(config, ctx)
69
- if "environment" in ctx.params and ctx.params["environment"] not in ctx.obj["server_envs"]:
80
+ if (
81
+ "environment" in ctx.params
82
+ and ctx.params["environment"] not in ctx.obj["server_envs"]
83
+ and ctx.params["environment"] is not None
84
+ ):
70
85
  click.echo(f"Invalid environment: {ctx.params['environment']}")
71
86
  click.echo(f"Available environments as defined in config file are: {list(ctx.obj['server_envs'])}")
72
87
  ctx.exit(1)
@@ -75,6 +90,29 @@ def load_config(f: Callable) -> Callable:
75
90
  return update_wrapper(wrapper, f)
76
91
 
77
92
 
93
+ def fail_if_no_artifacts_provider(f: Callable) -> Callable:
94
+ """
95
+ This function is only to be used as a decorator for various xsoar-cli subcommands, and only AFTER the load_config decorator has been called.
96
+ The intention is to fail gracefully if any subcommand is executed which requires an artifacts provider."
97
+ """
98
+
99
+ @click.pass_context
100
+ def wrapper(ctx: click.Context, *args, **kwargs) -> Callable: # noqa: ANN002, ANN003
101
+ if "environment" in ctx.params: # noqa: SIM108
102
+ key = ctx.params["environment"]
103
+ else:
104
+ key = ctx.obj["default_environment"]
105
+
106
+ with contextlib.suppress(KeyError):
107
+ location = ctx.obj["server_envs"][key].get("artifacts_location", None)
108
+ if not location:
109
+ click.echo("Command requires artifacts repository, but no artifacts_location defined in config.")
110
+ ctx.exit(1)
111
+ return ctx.invoke(f, *args, **kwargs)
112
+
113
+ return update_wrapper(wrapper, f)
114
+
115
+
78
116
  def parse_config(config: dict, ctx: click.Context) -> None:
79
117
  # Set the two XSOAR client objects in Click Context for use in later functions
80
118
  ctx.obj = {}
@@ -83,7 +121,8 @@ def parse_config(config: dict, ctx: click.Context) -> None:
83
121
  ctx.obj["default_new_case_type"] = config["default_new_case_type"]
84
122
  ctx.obj["server_envs"] = {}
85
123
  for key in config["server_config"]:
86
- ctx.obj["server_envs"][key] = Client(
124
+ ctx.obj["server_envs"][key] = {}
125
+ ctx.obj["server_envs"][key]["xsoar_client"] = Client(
87
126
  api_token=config["server_config"][key]["api_token"],
88
127
  server_url=config["server_config"][key]["base_url"],
89
128
  verify_ssl=config["server_config"][key]["verify_ssl"],
@@ -93,3 +132,4 @@ def parse_config(config: dict, ctx: click.Context) -> None:
93
132
  artifacts_location=config["server_config"][key].get("artifacts_location", None),
94
133
  s3_bucket_name=config["server_config"][key].get("s3_bucket_name", None),
95
134
  )
135
+ ctx.obj["server_envs"][key]["artifacts_location"] = config["server_config"][key].get("artifacts_location", None)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes