xsoar-cli 1.0.9__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.
@@ -0,0 +1,271 @@
1
+ import json
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import click
7
+
8
+ from xsoar_cli.utilities import load_config
9
+
10
+ if TYPE_CHECKING:
11
+ from xsoar_client.xsoar_client import Client
12
+
13
+
14
+ def load_manifest(manifest: str): # noqa: ANN201
15
+ """Calls json.load() on the manifest and returns a dict."""
16
+ filepath = Path(manifest)
17
+ try:
18
+ return json.load(filepath.open("r"))
19
+ except json.JSONDecodeError:
20
+ msg = f"Failed to decode JSON in {filepath}"
21
+ click.echo(msg)
22
+ sys.exit(1)
23
+ except FileNotFoundError:
24
+ print(f"File not found: {filepath}")
25
+ sys.exit(1)
26
+
27
+
28
+ def write_manifest(manifest: str, data: Any) -> None: # noqa: ANN401
29
+ """Writes the xsoar_conf.json manifest using json.dumps()"""
30
+ manifest_path = Path(manifest)
31
+ with manifest_path.open("w") as f:
32
+ f.write(json.dumps(data, indent=4))
33
+ f.write("\n")
34
+ click.echo(f"Written updated manifest to '{manifest_path}'")
35
+
36
+
37
+ @click.group()
38
+ def manifest() -> None:
39
+ """Various commands to interact/update/deploy content packs defined in the xsoar_config.json manifest."""
40
+
41
+
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.")
70
+ @click.argument("manifest", type=str)
71
+ @click.command()
72
+ @click.pass_context
73
+ @load_config
74
+ def update(ctx: click.Context, environment: str | None, manifest: str) -> None:
75
+ """Update manifest on disk with latest available content pack versions."""
76
+ if not environment:
77
+ environment = ctx.obj["default_environment"]
78
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
79
+ manifest_data = load_manifest(manifest)
80
+ click.echo("Fetching outdated packs from XSOAR server. This may take a minute...", nl=False)
81
+ results = xsoar_client.get_outdated_packs()
82
+ click.echo("done.")
83
+ if not results:
84
+ click.echo("No packs eligible for upgrade.")
85
+ sys.exit(0)
86
+
87
+ item1 = "Pack ID"
88
+ item2 = "Installed version"
89
+ item3 = "Latest available version"
90
+ header = f"{item1:50}{item2:20}{item3:20}"
91
+ click.echo(header)
92
+ for pack in results:
93
+ click.echo(f"{pack['id']:50}{pack['currentVersion']:20}{pack['latest']:20}")
94
+ click.echo(f"Total number of outdated content packs: {len(results)}")
95
+
96
+ for pack in results:
97
+ key = "custom_packs" if pack["author"] in ctx.obj["custom_pack_authors"] else "marketplace_packs"
98
+ index = next((i for i, item in enumerate(manifest_data[key]) if item["id"] == pack["id"]), None)
99
+ if index is None:
100
+ msg = f"Pack {pack['id']} not found in manifest."
101
+ click.echo(msg)
102
+ sys.exit(1)
103
+ comment = manifest_data[key][index].get("_comment", None)
104
+ if comment is not None:
105
+ print(f"WARNING: comment found in manifest for {pack['id']}: {comment}")
106
+ msg = f"Upgrade {pack['id']} from {pack['currentVersion']} to {pack['latest']}?"
107
+ should_upgrade = click.confirm(msg, default=True)
108
+ if should_upgrade:
109
+ manifest_data[key][index]["version"] = pack["latest"]
110
+ write_manifest(manifest, manifest_data)
111
+
112
+
113
+ @click.option("--environment", default=None, help="Default environment set in config file.")
114
+ @click.option("--mode", type=click.Choice(["full", "diff"]), default="diff", help="Validate the full manifest, or only the definitions that diff with installed versions")
115
+ @click.argument("manifest", type=str)
116
+ @click.command()
117
+ @click.pass_context
118
+ @load_config
119
+ def validate(ctx: click.Context, environment: str | None, mode: str, manifest: str) -> None:
120
+ """Validate manifest JSON and content pack availability by doing HTTP CONNECT to the appropriate artifacts repository.
121
+ Custom pack availability is implementation dependant."""
122
+ if not environment:
123
+ environment = ctx.obj["default_environment"]
124
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
125
+
126
+ manifest_data = load_manifest(manifest)
127
+ click.echo("Manifest is valid JSON")
128
+ keys = ["custom_packs", "marketplace_packs"]
129
+
130
+
131
+ def found_in_local_filesystem() -> bool:
132
+ # If we are in a merge request and the merge request contains a new pack as well
133
+ # as a updated xsoar_config.json manifest, then the pack is not available in S3
134
+ # before the MR is merged. Check if we can find the appropriate pack version locally
135
+ # If so, we can ignore this error because the pack will become available after merge.
136
+ manifest_arg = Path(manifest)
137
+ manifest_path = manifest_arg.resolve()
138
+ repo_path = manifest_path.parent
139
+ pack_metadata_path = Path(f"{repo_path}/Packs/{pack['id']}/pack_metadata.json")
140
+ with Path.open(pack_metadata_path, encoding="utf-8") as f:
141
+ pack_metadata = json.load(f)
142
+ if pack_metadata["currentVersion"] == pack["version"]:
143
+ return True
144
+ # The relevant Pack locally does not have the requested version.
145
+ return False
146
+
147
+ if mode == "full":
148
+ for key in keys:
149
+ custom = key == "custom_packs"
150
+ click.echo(f"Checking {key} availability ", nl=False)
151
+ for pack in manifest_data[key]:
152
+ available = xsoar_client.is_pack_available(pack_id=pack["id"], version=pack["version"], custom=custom)
153
+ # We check if a pack is found in local filesystem regardless of whether it's an upstream pack or not.
154
+ # This should cause any significantly negative performance penalties.
155
+ if not available and not found_in_local_filesystem():
156
+ click.echo(f"\nFailed to reach pack {pack['id']} version {pack['version']}")
157
+ sys.exit(1)
158
+ click.echo(".", nl=False)
159
+ print()
160
+ click.echo("Manifest is valid JSON and all packs are reachable")
161
+ return
162
+ elif mode == "diff":
163
+ installed_packs = xsoar_client.get_installed_packs()
164
+ for key in keys:
165
+ found_diff = False
166
+ custom = key == "custom_packs"
167
+ click.echo(f"Checking {key} availability ", nl=False)
168
+ for pack in manifest_data[key]:
169
+ installed = next((item for item in installed_packs if item["id"] == pack["id"]), {})
170
+ if not installed or installed["currentVersion"] != pack["version"]:
171
+ available = xsoar_client.is_pack_available(pack_id=pack["id"], version=pack["version"], custom=custom)
172
+ # We check if a pack is found in local filesystem regardless of whether it's an upstream pack or not.
173
+ # This should cause any significantly negative performance penalties.
174
+ if not available and not found_in_local_filesystem():
175
+ click.echo(f"\nFailed to reach pack {pack['id']} version {pack['version']}")
176
+ sys.exit(1)
177
+ click.echo(".", nl=False)
178
+ found_diff = True
179
+ if not found_diff:
180
+ click.echo("- no diff from installed versions found in manifest.")
181
+ else:
182
+ print()
183
+
184
+ click.echo("Manifest is valid JSON and all packs are reachable.")
185
+ return
186
+ else:
187
+ msg = "Invalid value for --mode detected. This should never happen"
188
+ raise RuntimeError(msg)
189
+
190
+
191
+ @click.option("--environment", default=None, help="Default environment set in config file.")
192
+ @click.argument("manifest", type=str)
193
+ @click.command()
194
+ @click.pass_context
195
+ @load_config
196
+ def diff(ctx: click.Context, manifest: str, environment: str | None) -> None:
197
+ """Prints out the differences (if any) between what is defined in the xsoar_config.json manifest and what is actually
198
+ installed on the XSOAR server."""
199
+ if not environment:
200
+ environment = ctx.obj["default_environment"]
201
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
202
+ manifest_data = load_manifest(manifest)
203
+ installed_packs = xsoar_client.get_installed_packs()
204
+ all_good = True
205
+ for key in manifest_data:
206
+ for pack in manifest_data[key]:
207
+ installed = next((item for item in installed_packs if item["id"] == pack["id"]), {})
208
+ if not installed:
209
+ click.echo(f"Pack {pack['id']} is not installed")
210
+ all_good = False
211
+ elif installed["currentVersion"] != pack["version"]:
212
+ msg = f"Manifest states {pack['id']} version {pack['version']} but version {installed['currentVersion']} is installed"
213
+ click.echo(msg)
214
+ all_good = False
215
+ if all_good:
216
+ click.echo("All packs up to date.")
217
+
218
+
219
+ @click.option("--environment", default=None, help="Default environment set in config file.")
220
+ @click.option("--verbose", is_flag=True, default=False)
221
+ @click.option("--yes", is_flag=True, default=False)
222
+ @click.command()
223
+ @click.argument("manifest", type=str)
224
+ @click.pass_context
225
+ @load_config
226
+ def deploy(ctx: click.Context, environment: str | None, manifest: str, verbose: bool, yes: bool) -> None: # noqa: FBT001
227
+ """
228
+ Deploys content packs to the XSOAR server as defined in the xsoar_config.json manifest.
229
+ The PATH argument expects the full or relative path to xsoar_config.json
230
+
231
+ \b
232
+ Prompts for confirmation prior to pack installation.
233
+ """
234
+ should_continue = True
235
+ if not yes:
236
+ should_continue = click.confirm(
237
+ f"WARNING: this operation will attempt to deploy all packs defined in the manifest to XSOAR {environment} environment. Continue?",
238
+ )
239
+ if not should_continue:
240
+ ctx.exit()
241
+ if not environment:
242
+ environment = ctx.obj["default_environment"]
243
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
244
+ manifest_data = load_manifest(manifest)
245
+ click.echo("Fetching installed packs...", err=True)
246
+ installed_packs = xsoar_client.get_installed_packs()
247
+ click.echo("done.")
248
+ none_installed = True
249
+ for key in manifest_data:
250
+ custom = key == "custom_packs"
251
+ for pack in manifest_data[key]:
252
+ installed = next((item for item in installed_packs if item["id"] == pack["id"]), {})
253
+ if not installed or installed["currentVersion"] != pack["version"]:
254
+ # Install pack
255
+ click.echo(f"Installing {pack['id']} version {pack['version']}...", nl=False)
256
+ xsoar_client.deploy_pack(pack_id=pack["id"], pack_version=pack["version"], custom=custom)
257
+ click.echo("OK.")
258
+ none_installed = False
259
+ elif verbose:
260
+ click.echo(f"Not installing {pack['id']} version {pack['version']}. Already installed.")
261
+ # Print message that install is skipped
262
+
263
+ if none_installed:
264
+ click.echo("No packs to install. All packs and versions in manifest is already installed on XSOAR server.")
265
+
266
+
267
+ manifest.add_command(deploy)
268
+ manifest.add_command(diff)
269
+ manifest.add_command(update)
270
+ manifest.add_command(validate)
271
+ manifest.add_command(generate)
@@ -0,0 +1,36 @@
1
+ # Pack
2
+
3
+ Content pack management commands for XSOAR.
4
+
5
+ ## Delete
6
+
7
+ Delete a content pack from the XSOAR server. Verifies the pack is installed before attempting deletion.
8
+
9
+ **Syntax:** `xsoar-cli pack delete [OPTIONS] PACK_ID`
10
+
11
+ **Options:**
12
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
13
+
14
+ **Arguments:**
15
+ - `PACK_ID` - The ID of the content pack to delete
16
+
17
+ **Examples:**
18
+ ```
19
+ xsoar-cli pack delete MyCustomPack
20
+ xsoar-cli pack delete --environment prod CommonScripts
21
+ ```
22
+
23
+ ## Get Outdated
24
+
25
+ Display a list of outdated content packs showing current and latest available versions in table format.
26
+
27
+ **Syntax:** `xsoar-cli pack get-outdated [OPTIONS]`
28
+
29
+ **Options:**
30
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
31
+
32
+ **Examples:**
33
+ ```
34
+ xsoar-cli pack get-outdated
35
+ xsoar-cli pack get-outdated --environment staging
36
+ ```
File without changes
@@ -0,0 +1,59 @@
1
+ import sys
2
+ from typing import TYPE_CHECKING
3
+
4
+ import click
5
+
6
+ from xsoar_cli.utilities import load_config
7
+
8
+ if TYPE_CHECKING:
9
+ from xsoar_client.xsoar_client import Client
10
+
11
+
12
+ @click.group()
13
+ @click.pass_context
14
+ def pack(ctx: click.Context) -> None:
15
+ """Various content pack related commands."""
16
+
17
+
18
+ @click.option("--environment", default=None, help="Default environment set in config file.")
19
+ @click.command()
20
+ @click.argument("pack_id", type=str)
21
+ @click.pass_context
22
+ @load_config
23
+ def delete(ctx: click.Context, environment: str | None, pack_id: str) -> None:
24
+ """Deletes a content pack from the XSOAR server."""
25
+ if not environment:
26
+ environment = ctx.obj["default_environment"]
27
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
28
+ if not xsoar_client.is_installed(pack_id=pack_id):
29
+ click.echo(f"Pack ID {pack_id} is not installed. Cannot delete.")
30
+ sys.exit(1)
31
+ xsoar_client.delete(pack_id=pack_id)
32
+ click.echo(f"Deleted pack {pack_id} from XSOAR {environment}")
33
+
34
+
35
+ @click.option("--environment", default=None, help="Default environment set in config file.")
36
+ @click.command()
37
+ @click.pass_context
38
+ @load_config
39
+ def get_outdated(ctx: click.Context, environment: str | None) -> None:
40
+ """Prints out a list of outdated content packs."""
41
+ if not environment:
42
+ environment = ctx.obj["default_environment"]
43
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
44
+ click.echo("Fetching outdated packs. This may take a little while...", err=True)
45
+ outdated_packs = xsoar_client.get_outdated_packs()
46
+ if not outdated_packs:
47
+ click.echo("No outdated packs found")
48
+ sys.exit(0)
49
+ id_header = "Pack ID"
50
+ installed_header = "Installed version"
51
+ latest_header = "Latest version"
52
+ click.echo(f"{id_header:<50}{installed_header:>17}{latest_header:>17}")
53
+ for pack in outdated_packs:
54
+ msg = f"{pack['id']:<50}{pack['currentVersion']:>17}{pack['latest']:>17}"
55
+ click.echo(msg)
56
+
57
+
58
+ pack.add_command(delete)
59
+ pack.add_command(get_outdated)
@@ -0,0 +1,43 @@
1
+ # Playbook
2
+
3
+ Playbook management commands for XSOAR development workflows.
4
+
5
+ ## Download
6
+
7
+ Download a playbook from XSOAR, format it with demisto-sdk, and re-attach it to the server. Designed for content repository development workflows.
8
+
9
+ **Syntax:** `xsoar-cli playbook download [OPTIONS] NAME`
10
+
11
+ **Options:**
12
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
13
+
14
+ **Arguments:**
15
+ - `NAME` - The name of the playbook to download
16
+
17
+ **Examples:**
18
+ ```
19
+ xsoar-cli playbook download "My Awesome Playbook"
20
+ xsoar-cli playbook download --environment dev "Security Investigation"
21
+ ```
22
+
23
+ ## Requirements
24
+
25
+ - Must be run from the root of a content repository with proper directory structure
26
+ - Target directory `Packs/<PackID>/Playbooks/` must exist
27
+ - `demisto-sdk` must be installed and available in PATH
28
+
29
+ ## Behavior
30
+
31
+ 1. Downloads the specified playbook from XSOAR
32
+ 2. Detects the content pack ID from playbook metadata
33
+ 3. Saves to `$(cwd)/Packs/<PackID>/Playbooks/<playbook_name>.yml`
34
+ 4. Runs `demisto-sdk format --assume-yes --no-validate --no-graph` on the file
35
+ 5. Re-attaches the formatted playbook to XSOAR
36
+ 6. Replaces whitespace characters in filenames with underscores
37
+
38
+ ## Limitations
39
+
40
+ - Only supports playbooks that are already part of a content pack
41
+ - Requires existing content repository directory structure
42
+ - Attempting to download non-existing playbooks results in server errors
43
+ - Does not support completely new playbooks (not yet implemented)
File without changes
@@ -0,0 +1,84 @@
1
+ import pathlib
2
+ import subprocess
3
+ import sys
4
+ from io import StringIO
5
+ from typing import TYPE_CHECKING
6
+
7
+ import click
8
+ import yaml
9
+
10
+ from xsoar_cli.utilities import load_config
11
+
12
+ if TYPE_CHECKING:
13
+ from xsoar_client.xsoar_client import Client
14
+
15
+
16
+ @click.group()
17
+ @click.pass_context
18
+ def playbook(ctx: click.Context) -> None:
19
+ """Download/attach/detach playbooks"""
20
+
21
+
22
+ @click.option(
23
+ "--environment", default=None, help="Default environment set in config file."
24
+ )
25
+ @click.command()
26
+ @click.argument("name", type=str)
27
+ @click.pass_context
28
+ @load_config
29
+ def download(ctx: click.Context, environment: str | None, name: str) -> None:
30
+ """Download and reattach playbook.
31
+
32
+ We try to detect output path to $(cwd)/Packs/<Pack ID>/Playbooks/<name>.yml
33
+ Whitespace in Pack ID and playbook filename will be replaced with underscores. After the playbook is downloaded,
34
+ then demisto-sdk format --assume-yes --no-validate --no-graph is done on the downloaded playbook before the item
35
+ is re-attached in XSOAR.
36
+ """
37
+ if not environment:
38
+ environment = ctx.obj["default_environment"]
39
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
40
+ # Maybe we should search for the playbook before attempting download in
41
+ # case user specifies a cutsom playbook and not a system playbook
42
+ try:
43
+ click.echo("Downloading playbook...", nl=False)
44
+ playbook = xsoar_client.download_item(item_type="playbook", item_id=name)
45
+ click.echo("ok.")
46
+ except Exception as ex: # noqa: BLE001
47
+ click.echo(f"FAILED: {ex!s}")
48
+ sys.exit(1)
49
+ playbook_bytes_data = StringIO(playbook.decode("utf-8"))
50
+ playbook_data = yaml.safe_load(playbook_bytes_data)
51
+ pack_id = playbook_data["contentitemexportablefields"]["contentitemfields"][
52
+ "packID"
53
+ ]
54
+ cwd = pathlib.Path().cwd()
55
+ target_dir = pathlib.Path(cwd / "Packs" / pack_id / "Playbooks")
56
+ if not target_dir.is_dir():
57
+ msg = f"Cannot find target directory: {target_dir}\nMaybe you're not running xsoar-cli from the root of a content repository?"
58
+ click.echo(msg)
59
+ sys.exit(1)
60
+ filepath = pathlib.Path(
61
+ cwd / "Packs" / pack_id / "Playbooks" / f"{playbook_data['id']}.yml"
62
+ )
63
+ filepath = pathlib.Path(str(filepath).replace(" ", "_"))
64
+ with filepath.open("w") as f:
65
+ yaml.dump(playbook_data, f, default_flow_style=False)
66
+ click.echo(f"Written playbook to: {filepath}")
67
+ click.echo("Running demisto-sdk format on newly downloaded playbook")
68
+ subprocess.run(
69
+ [
70
+ "demisto-sdk",
71
+ "format",
72
+ "--assume-yes",
73
+ "--no-validate",
74
+ "--no-graph",
75
+ str(filepath),
76
+ ],
77
+ check=False,
78
+ ) # noqa: S603, S607
79
+ click.echo("Re-attaching playbook in XSOAR...", nl=False)
80
+ xsoar_client.attach_item(item_type="playbook", item_id=name)
81
+ click.echo("done.")
82
+
83
+
84
+ playbook.add_command(download)
@@ -0,0 +1,85 @@
1
+ # XSOAR CLI Plugin System
2
+
3
+ The XSOAR CLI plugin system allows you to extend the CLI with custom commands. Plugins are Python files placed in `~/.local/xsoar-cli/plugins/` that are automatically discovered and loaded.
4
+
5
+ ## Quick Start
6
+
7
+ 1. **Create the plugins directory**:
8
+ ```bash
9
+ mkdir -p ~/.local/xsoar-cli/plugins
10
+ ```
11
+
12
+ 2. **Create a plugin file** (`~/.local/xsoar-cli/plugins/hello_plugin.py`):
13
+ ```python
14
+ import click
15
+
16
+ class HelloPlugin(XSOARPlugin):
17
+ @property
18
+ def name(self) -> str:
19
+ return "hello"
20
+
21
+ @property
22
+ def version(self) -> str:
23
+ return "1.0.0"
24
+
25
+ def get_command(self) -> click.Command:
26
+ @click.command(help="Say hello")
27
+ @click.option("--name", default="World", help="Name to greet")
28
+ def hello(name: str):
29
+ click.echo(f"Hello, {name}!")
30
+ return hello
31
+ ```
32
+
33
+ 3. **Use your plugin**:
34
+ ```bash
35
+ xsoar-cli hello --name "Alice"
36
+ ```
37
+
38
+ ## Plugin Structure
39
+
40
+ A plugin must:
41
+ - Be a Python class that inherits from `XSOARPlugin`
42
+ - Implement the `name`, `version`, and `get_command()` methods
43
+ - The `get_command()` method must return a Click command
44
+
45
+ ### Required Methods
46
+
47
+ - `name` - Unique identifier for your plugin
48
+ - `version` - Version string
49
+ - `get_command()` - Returns the Click command to register
50
+
51
+ ### Optional Methods
52
+
53
+ - `description` - Plugin description
54
+ - `initialize()` - Called when plugin loads
55
+ - `cleanup()` - Called when plugin unloads
56
+
57
+ ## Plugin Management
58
+
59
+ ```bash
60
+ # List all plugins
61
+ xsoar-cli plugins list
62
+
63
+ # Show plugin details
64
+ xsoar-cli plugins info hello
65
+
66
+ # Validate plugins
67
+ xsoar-cli plugins validate
68
+
69
+ # Reload a plugin after changes
70
+ xsoar-cli plugins reload hello
71
+ ```
72
+
73
+ ## Command Conflicts
74
+
75
+ Plugin commands cannot use the same names as core CLI commands:
76
+ - `case`, `config`, `graph`, `manifest`, `pack`, `playbook`, `plugins`
77
+
78
+ If conflicts occur, use a different command name or create a command group.
79
+
80
+ ## Notes
81
+
82
+ - The `XSOARPlugin` class is automatically available in plugin files
83
+ - Plugin files are discovered automatically from `~/.local/xsoar-cli/plugins/`
84
+ - Plugins are loaded when the CLI starts
85
+ - No special imports are needed for `XSOARPlugin`
@@ -0,0 +1,68 @@
1
+ """
2
+ XSOAR CLI Plugin System
3
+
4
+ This module provides the infrastructure for creating and loading plugins
5
+ for the xsoar-cli application. Plugins can extend the CLI with custom
6
+ commands and functionality.
7
+ """
8
+
9
+ from abc import ABC, abstractmethod
10
+ from typing import Optional
11
+
12
+ import click
13
+
14
+
15
+ class XSOARPlugin(ABC):
16
+ """
17
+ Abstract base class for XSOAR CLI plugins.
18
+
19
+ All plugins should inherit from this class and implement the required methods.
20
+ """
21
+
22
+ @property
23
+ @abstractmethod
24
+ def name(self) -> str:
25
+ """Return the plugin name."""
26
+
27
+ @property
28
+ @abstractmethod
29
+ def version(self) -> str:
30
+ """Return the plugin version."""
31
+
32
+ @property
33
+ def description(self) -> Optional[str]:
34
+ """Return an optional description of the plugin."""
35
+ return None
36
+
37
+ @abstractmethod
38
+ def get_command(self) -> click.Command:
39
+ """
40
+ Return the Click command or command group that this plugin provides.
41
+
42
+ Returns:
43
+ click.Command: The command to be registered with the CLI
44
+ """
45
+
46
+ def initialize(self) -> None:
47
+ """
48
+ Initialize the plugin. Called once when the plugin is loaded.
49
+ Override this method if your plugin needs initialization.
50
+ """
51
+
52
+ def cleanup(self) -> None:
53
+ """
54
+ Cleanup plugin resources. Called when the application shuts down.
55
+ Override this method if your plugin needs cleanup.
56
+ """
57
+
58
+
59
+ class PluginError(Exception):
60
+ """Exception raised when there's an error with plugin loading or execution."""
61
+
62
+
63
+ class PluginLoadError(PluginError):
64
+ """Exception raised when a plugin fails to load."""
65
+
66
+
67
+ class PluginRegistrationError(PluginError):
68
+ """Exception raised when a plugin fails to register."""