xsoar-cli 1.0.3__py3-none-any.whl → 1.0.5__py3-none-any.whl
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.
- xsoar_cli/__about__.py +1 -1
- xsoar_cli/case/commands.py +28 -9
- xsoar_cli/config/commands.py +12 -2
- xsoar_cli/manifest/commands.py +49 -14
- xsoar_cli/pack/commands.py +10 -6
- xsoar_cli/playbook/commands.py +4 -3
- xsoar_cli/plugins/README.md +23 -23
- xsoar_cli/utilities.py +42 -2
- {xsoar_cli-1.0.3.dist-info → xsoar_cli-1.0.5.dist-info}/METADATA +4 -2
- {xsoar_cli-1.0.3.dist-info → xsoar_cli-1.0.5.dist-info}/RECORD +13 -13
- {xsoar_cli-1.0.3.dist-info → xsoar_cli-1.0.5.dist-info}/WHEEL +0 -0
- {xsoar_cli-1.0.3.dist-info → xsoar_cli-1.0.5.dist-info}/entry_points.txt +0 -0
- {xsoar_cli-1.0.3.dist-info → xsoar_cli-1.0.5.dist-info}/licenses/LICENSE.txt +0 -0
xsoar_cli/__about__.py
CHANGED
xsoar_cli/case/commands.py
CHANGED
|
@@ -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=
|
|
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
|
-
|
|
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=
|
|
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(
|
|
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
|
-
|
|
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"]
|
xsoar_cli/config/commands.py
CHANGED
|
@@ -8,7 +8,13 @@ if TYPE_CHECKING:
|
|
|
8
8
|
|
|
9
9
|
import contextlib
|
|
10
10
|
|
|
11
|
-
from xsoar_cli.utilities import
|
|
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
|
|
xsoar_cli/manifest/commands.py
CHANGED
|
@@ -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=
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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.
|
|
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)
|
xsoar_cli/pack/commands.py
CHANGED
|
@@ -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=
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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:
|
xsoar_cli/playbook/commands.py
CHANGED
|
@@ -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=
|
|
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
|
xsoar_cli/plugins/README.md
CHANGED
|
@@ -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.
|
xsoar_cli/utilities.py
CHANGED
|
@@ -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
|
|
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] =
|
|
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)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xsoar-cli
|
|
3
|
-
Version: 1.0.
|
|
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:
|
|
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
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
xsoar_cli/__about__.py,sha256=
|
|
1
|
+
xsoar_cli/__about__.py,sha256=WJ_NW-EdxC6N1rZ9OTTVUtKWWLaNJ0AvBoPxIYQkuYc,127
|
|
2
2
|
xsoar_cli/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
3
3
|
xsoar_cli/cli.py,sha256=iAlSeZe2iR6ciTVrJYLt-CDZk7b5I-hzHFXbYoXhupA,1342
|
|
4
|
-
xsoar_cli/utilities.py,sha256=
|
|
4
|
+
xsoar_cli/utilities.py,sha256=ZK0rW4jl1hSbphU_auuuBgwUSmDDP9kq3ZoAFp6D-3g,5697
|
|
5
5
|
xsoar_cli/case/README.md,sha256=MTfgVeW3qJXRPNFo8CkZvulm2vwbN8sgiW86V-qXRFw,1342
|
|
6
6
|
xsoar_cli/case/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
xsoar_cli/case/commands.py,sha256=
|
|
7
|
+
xsoar_cli/case/commands.py,sha256=fdAqX_anYkyRKzzoUNZVYhkTJBqBieUXJX5QqpfEdbE,4255
|
|
8
8
|
xsoar_cli/config/README.md,sha256=pcO858PDL9c0qtwj3_a6B8q2CGvcka3dwclVnwi2vlA,516
|
|
9
9
|
xsoar_cli/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
xsoar_cli/config/commands.py,sha256=
|
|
10
|
+
xsoar_cli/config/commands.py,sha256=i6lWaR0LjAzMpNVPZyCsDPaT0GMVqTUXdau6Id0iOrs,4193
|
|
11
11
|
xsoar_cli/graph/README.md,sha256=kyWIGs2Sd-OdqAaCWJjyvGpAhXhFcuqQwVqFBgzgWzk,861
|
|
12
12
|
xsoar_cli/graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
xsoar_cli/graph/commands.py,sha256=LKOpUu8r6KThJ5HdyjZlhpwLKRgMcEu7btBOQXwDkMs,1069
|
|
14
14
|
xsoar_cli/manifest/README.md,sha256=0oiA6rZEAUQMOYM7VmtUBtW3PRo7-exfkjw5JLt_whU,9282
|
|
15
15
|
xsoar_cli/manifest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
xsoar_cli/manifest/commands.py,sha256=
|
|
16
|
+
xsoar_cli/manifest/commands.py,sha256=nd7-xIJs9qda5iXfFvyfXUG6gctbW9yeUry5KWltisc,10127
|
|
17
17
|
xsoar_cli/pack/README.md,sha256=CA7jAEphHxK0gh58rLRKL-u3wx29QgNAXojd_tGBXnY,46
|
|
18
18
|
xsoar_cli/pack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
xsoar_cli/pack/commands.py,sha256=
|
|
19
|
+
xsoar_cli/pack/commands.py,sha256=1vGF8_mMemZt75qCBTau_M3dihSr79vc2vea8xr5h_Y,2063
|
|
20
20
|
xsoar_cli/playbook/README.md,sha256=8y_YhvZtLP7KzYG83jiVBF-wBAEh8UTJcgmTOTynmbc,977
|
|
21
21
|
xsoar_cli/playbook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
xsoar_cli/playbook/commands.py,sha256=
|
|
23
|
-
xsoar_cli/plugins/README.md,sha256=
|
|
22
|
+
xsoar_cli/playbook/commands.py,sha256=fZnL5L3pG2gKC4h6q6jmfOQFTa0Tpwnn4e8E5YhDbk0,2794
|
|
23
|
+
xsoar_cli/plugins/README.md,sha256=pxBZlEvWyhRhS4x79dPudYLJHctzuiVxLXBlQ8o2BPI,11236
|
|
24
24
|
xsoar_cli/plugins/__init__.py,sha256=81IZsMbZsqrLdB6TjA9t6s3yS8FkuihliBFX4xZUpTo,1753
|
|
25
25
|
xsoar_cli/plugins/commands.py,sha256=HC0sWu149uQG9Ztag4t2CNPKXTM4WJbEdLSvFMEjw80,10660
|
|
26
26
|
xsoar_cli/plugins/manager.py,sha256=7RPk3lAYDifGMLOU-hFOqyPxTVk8ibBVzBqH7R8wy4g,13012
|
|
27
|
-
xsoar_cli-1.0.
|
|
28
|
-
xsoar_cli-1.0.
|
|
29
|
-
xsoar_cli-1.0.
|
|
30
|
-
xsoar_cli-1.0.
|
|
31
|
-
xsoar_cli-1.0.
|
|
27
|
+
xsoar_cli-1.0.5.dist-info/METADATA,sha256=PQLPj12eaIU_U0WJWW07jmVl-5gQmIJLFjdvNihGPfQ,9218
|
|
28
|
+
xsoar_cli-1.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
29
|
+
xsoar_cli-1.0.5.dist-info/entry_points.txt,sha256=s6Klu4QRekXsmZaBxMyFlE4Q-4_jIA9uijk4qIYUPvE,48
|
|
30
|
+
xsoar_cli-1.0.5.dist-info/licenses/LICENSE.txt,sha256=l6xnqWKshqwwTXt6ayO6MX8Uvygq0YnkUuFTNnR3ba4,1097
|
|
31
|
+
xsoar_cli-1.0.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|