xsoar-cli 0.0.3__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 +4 -0
- xsoar_cli/__init__.py +1 -0
- xsoar_cli/case/README.md +31 -0
- xsoar_cli/case/__init__.py +0 -0
- xsoar_cli/case/commands.py +86 -0
- xsoar_cli/cli.py +41 -0
- xsoar_cli/config/README.md +12 -0
- xsoar_cli/config/__init__.py +0 -0
- xsoar_cli/config/commands.py +99 -0
- xsoar_cli/graph/README.md +17 -0
- xsoar_cli/graph/__init__.py +0 -0
- xsoar_cli/graph/commands.py +32 -0
- xsoar_cli/manifest/README.md +23 -0
- xsoar_cli/manifest/__init__.py +0 -0
- xsoar_cli/manifest/commands.py +200 -0
- xsoar_cli/pack/README.md +7 -0
- xsoar_cli/pack/__init__.py +0 -0
- xsoar_cli/pack/commands.py +55 -0
- xsoar_cli/playbook/README.md +19 -0
- xsoar_cli/playbook/__init__.py +0 -0
- xsoar_cli/playbook/commands.py +67 -0
- xsoar_cli/plugins/README.md +433 -0
- xsoar_cli/plugins/__init__.py +68 -0
- xsoar_cli/plugins/commands.py +296 -0
- xsoar_cli/plugins/manager.py +399 -0
- xsoar_cli/utilities.py +94 -0
- xsoar_cli-0.0.3.dist-info/METADATA +128 -0
- xsoar_cli-0.0.3.dist-info/RECORD +31 -0
- xsoar_cli-0.0.3.dist-info/WHEEL +4 -0
- xsoar_cli-0.0.3.dist-info/entry_points.txt +2 -0
- xsoar_cli-0.0.3.dist-info/licenses/LICENSE.txt +9 -0
|
@@ -0,0 +1,67 @@
|
|
|
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("--environment", default="dev", show_default=True, help="Environment as defined in config file")
|
|
23
|
+
@click.command()
|
|
24
|
+
@click.argument("name", type=str)
|
|
25
|
+
@click.pass_context
|
|
26
|
+
@load_config
|
|
27
|
+
def download(ctx: click.Context, environment: str, name: str) -> None:
|
|
28
|
+
"""Download and reattach playbook.
|
|
29
|
+
|
|
30
|
+
We try to detect output path to $(cwd)/Packs/<Pack ID>/Playbooks/<name>.yml
|
|
31
|
+
Whitespace in Pack ID and playbook filename will be replaced with underscores. After the playbook is downloaded,
|
|
32
|
+
then demisto-sdk format --assume-yes --no-validate --no-graph is done on the downloaded playbook before the item
|
|
33
|
+
is re-attached in XSOAR.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
xsoar_client: Client = ctx.obj["server_envs"][environment]
|
|
37
|
+
# Maybe we should search for the playbook before attempting download in
|
|
38
|
+
# case user specifies a cutsom playbook and not a system playbook
|
|
39
|
+
try:
|
|
40
|
+
click.echo("Downloading playbook...", nl=False)
|
|
41
|
+
playbook = xsoar_client.download_item(item_type="playbook", item_id=name)
|
|
42
|
+
click.echo("ok.")
|
|
43
|
+
except Exception as ex: # noqa: BLE001
|
|
44
|
+
click.echo(f"FAILED: {ex!s}")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
playbook_bytes_data = StringIO(playbook.decode("utf-8"))
|
|
47
|
+
playbook_data = yaml.safe_load(playbook_bytes_data)
|
|
48
|
+
pack_id = playbook_data["contentitemexportablefields"]["contentitemfields"]["packID"]
|
|
49
|
+
cwd = pathlib.Path().cwd()
|
|
50
|
+
target_dir = pathlib.Path(cwd / "Packs" / pack_id / "Playbooks")
|
|
51
|
+
if not target_dir.is_dir():
|
|
52
|
+
msg = f"Cannot find target directory: {target_dir}\nMaybe you're not running xsoar-cli from the root of a content repository?"
|
|
53
|
+
click.echo(msg)
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
filepath = pathlib.Path(cwd / "Packs" / pack_id / "Playbooks" / f"{playbook_data['id']}.yml")
|
|
56
|
+
filepath = pathlib.Path(str(filepath).replace(" ", "_"))
|
|
57
|
+
with filepath.open("w") as f:
|
|
58
|
+
yaml.dump(playbook_data, f, default_flow_style=False)
|
|
59
|
+
click.echo(f"Written playbook to: {filepath}")
|
|
60
|
+
click.echo("Running demisto-sdk format on newly downloaded playbook")
|
|
61
|
+
subprocess.run(["demisto-sdk", "format", "--assume-yes", "--no-validate", "--no-graph", str(filepath)], check=False) # noqa: S603, S607
|
|
62
|
+
click.echo("Re-attaching playbook in XSOAR...", nl=False)
|
|
63
|
+
xsoar_client.attach_item(item_type="playbook", item_id=name)
|
|
64
|
+
click.echo("done.")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
playbook.add_command(download)
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# XSOAR CLI Plugin System
|
|
2
|
+
|
|
3
|
+
The XSOAR CLI plugin system allows you to extend the CLI with custom commands and functionality. Plugins are Python files that you place in a special directory, and they're automatically discovered and loaded by the CLI.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
1. **Create the plugins directory** (if it doesn't exist):
|
|
8
|
+
```bash
|
|
9
|
+
mkdir -p ~/.local/xsoar-cli/plugins
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. **Create an example plugin**:
|
|
13
|
+
```bash
|
|
14
|
+
xsoar-cli plugins create-example
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
3. **Test the example plugin**:
|
|
18
|
+
```bash
|
|
19
|
+
xsoar-cli example hello --name "World"
|
|
20
|
+
xsoar-cli example info
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Plugin Directory
|
|
24
|
+
|
|
25
|
+
Plugins are stored in: `~/.local/xsoar-cli/plugins/`
|
|
26
|
+
|
|
27
|
+
Each plugin is a Python file (`.py`) in this directory. The CLI automatically discovers and loads all Python files in this directory when it starts.
|
|
28
|
+
|
|
29
|
+
## Creating a Plugin
|
|
30
|
+
|
|
31
|
+
### Basic Plugin Structure
|
|
32
|
+
|
|
33
|
+
A plugin is a Python class that inherits from `XSOARPlugin`. Here's the minimal structure:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import click
|
|
37
|
+
from xsoar_cli.plugins import XSOARPlugin
|
|
38
|
+
|
|
39
|
+
class MyPlugin(XSOARPlugin):
|
|
40
|
+
@property
|
|
41
|
+
def name(self) -> str:
|
|
42
|
+
return "myplugin"
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def version(self) -> str:
|
|
46
|
+
return "1.0.0"
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def description(self) -> str:
|
|
50
|
+
return "My custom plugin"
|
|
51
|
+
|
|
52
|
+
def get_command(self) -> click.Command:
|
|
53
|
+
@click.command(help="My custom command")
|
|
54
|
+
def mycommand():
|
|
55
|
+
click.echo("Hello from my plugin!")
|
|
56
|
+
|
|
57
|
+
return mycommand
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Complete Example
|
|
61
|
+
|
|
62
|
+
Here's a more comprehensive example (`~/.local/xsoar-cli/plugins/my_plugin.py`):
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
"""
|
|
66
|
+
My Custom XSOAR Plugin
|
|
67
|
+
|
|
68
|
+
This plugin demonstrates various features of the plugin system.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
import click
|
|
72
|
+
from xsoar_cli.plugins import XSOARPlugin
|
|
73
|
+
|
|
74
|
+
class MyCustomPlugin(XSOARPlugin):
|
|
75
|
+
@property
|
|
76
|
+
def name(self) -> str:
|
|
77
|
+
return "mycustom"
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def version(self) -> str:
|
|
81
|
+
return "1.0.0"
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def description(self) -> str:
|
|
85
|
+
return "A custom plugin with multiple commands"
|
|
86
|
+
|
|
87
|
+
def get_command(self) -> click.Command:
|
|
88
|
+
"""Return the main command group for this plugin."""
|
|
89
|
+
|
|
90
|
+
@click.group(help="My custom commands")
|
|
91
|
+
def mycustom():
|
|
92
|
+
"""Main command group for my custom plugin."""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
@click.command(help="Greet someone")
|
|
96
|
+
@click.option("--name", default="World", help="Name to greet")
|
|
97
|
+
@click.option("--times", default=1, help="Number of times to greet")
|
|
98
|
+
def greet(name: str, times: int):
|
|
99
|
+
"""Greet someone multiple times."""
|
|
100
|
+
for i in range(times):
|
|
101
|
+
click.echo(f"Hello, {name}!")
|
|
102
|
+
|
|
103
|
+
@click.command(help="Show current status")
|
|
104
|
+
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
|
|
105
|
+
def status(verbose: bool):
|
|
106
|
+
"""Show plugin status."""
|
|
107
|
+
click.echo(f"Plugin: {self.name} v{self.version}")
|
|
108
|
+
if verbose:
|
|
109
|
+
click.echo(f"Description: {self.description}")
|
|
110
|
+
click.echo("Status: Active")
|
|
111
|
+
|
|
112
|
+
@click.command(help="Process a file")
|
|
113
|
+
@click.argument("filename", type=click.Path(exists=True))
|
|
114
|
+
@click.option("--output", "-o", help="Output file")
|
|
115
|
+
def process(filename: str, output: str):
|
|
116
|
+
"""Process a file."""
|
|
117
|
+
click.echo(f"Processing file: {filename}")
|
|
118
|
+
if output:
|
|
119
|
+
click.echo(f"Output will be saved to: {output}")
|
|
120
|
+
# Your processing logic here
|
|
121
|
+
|
|
122
|
+
# Add commands to the group
|
|
123
|
+
mycustom.add_command(greet)
|
|
124
|
+
mycustom.add_command(status)
|
|
125
|
+
mycustom.add_command(process)
|
|
126
|
+
|
|
127
|
+
return mycustom
|
|
128
|
+
|
|
129
|
+
def initialize(self):
|
|
130
|
+
"""Initialize the plugin."""
|
|
131
|
+
click.echo("My custom plugin initialized!")
|
|
132
|
+
|
|
133
|
+
def cleanup(self):
|
|
134
|
+
"""Cleanup when the plugin is unloaded."""
|
|
135
|
+
pass
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
After saving this file, you can use it like:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
xsoar-cli mycustom greet --name John --times 3
|
|
142
|
+
xsoar-cli mycustom status --verbose
|
|
143
|
+
xsoar-cli mycustom process myfile.txt --output result.txt
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Plugin API Reference
|
|
147
|
+
|
|
148
|
+
### XSOARPlugin Base Class
|
|
149
|
+
|
|
150
|
+
All plugins must inherit from `XSOARPlugin` and implement the required methods:
|
|
151
|
+
|
|
152
|
+
#### Required Properties
|
|
153
|
+
|
|
154
|
+
- **`name`** (str): The plugin name, used for identification
|
|
155
|
+
- **`version`** (str): The plugin version
|
|
156
|
+
- **`description`** (str, optional): A description of what the plugin does
|
|
157
|
+
|
|
158
|
+
#### Required Methods
|
|
159
|
+
|
|
160
|
+
- **`get_command()`**: Must return a `click.Command` or `click.Group` object
|
|
161
|
+
|
|
162
|
+
#### Optional Methods
|
|
163
|
+
|
|
164
|
+
- **`initialize()`**: Called once when the plugin is loaded
|
|
165
|
+
- **`cleanup()`**: Called when the plugin is unloaded
|
|
166
|
+
|
|
167
|
+
### Using XSOAR CLI Utilities
|
|
168
|
+
|
|
169
|
+
Your plugins can access the same utilities that the core CLI uses:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from xsoar_cli.utilities import load_config
|
|
173
|
+
|
|
174
|
+
@click.command()
|
|
175
|
+
@click.pass_context
|
|
176
|
+
@load_config
|
|
177
|
+
def my_command(ctx: click.Context):
|
|
178
|
+
# Access XSOAR client
|
|
179
|
+
xsoar_client = ctx.obj["server_envs"]["dev"]
|
|
180
|
+
# Use the client...
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Plugin Management Commands
|
|
184
|
+
|
|
185
|
+
### List Plugins
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# List all plugins
|
|
189
|
+
xsoar-cli plugins list
|
|
190
|
+
|
|
191
|
+
# Show detailed information
|
|
192
|
+
xsoar-cli plugins list --verbose
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Check for Command Conflicts
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Check for conflicts between plugin commands and core CLI commands
|
|
199
|
+
xsoar-cli plugins check-conflicts
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Plugin Information
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Show information about a specific plugin
|
|
206
|
+
xsoar-cli plugins info my_plugin
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Validate Plugins
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Validate all plugins
|
|
213
|
+
xsoar-cli plugins validate
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Reload Plugin
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Reload a plugin after making changes
|
|
220
|
+
xsoar-cli plugins reload my_plugin
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Open Plugins Directory
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# Open the plugins directory in your file manager
|
|
227
|
+
xsoar-cli plugins open
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Command Conflicts
|
|
231
|
+
|
|
232
|
+
The plugin system automatically detects when a plugin tries to register a command that conflicts with existing core CLI commands.
|
|
233
|
+
|
|
234
|
+
### How Conflict Detection Works
|
|
235
|
+
|
|
236
|
+
1. **Automatic Detection**: When plugins are loaded, the system checks if any plugin command names conflict with core commands
|
|
237
|
+
2. **Conflict Prevention**: Conflicting plugin commands are **not registered** - the core command takes precedence
|
|
238
|
+
3. **User Notification**: Conflicts are reported through various commands
|
|
239
|
+
|
|
240
|
+
### Checking for Conflicts
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Check for any command conflicts
|
|
244
|
+
xsoar-cli plugins check-conflicts
|
|
245
|
+
|
|
246
|
+
# List plugins (shows conflicts in output)
|
|
247
|
+
xsoar-cli plugins list
|
|
248
|
+
|
|
249
|
+
# Validate all plugins (includes conflict checking)
|
|
250
|
+
xsoar-cli plugins validate
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Example Conflict Scenario
|
|
254
|
+
|
|
255
|
+
If you create a plugin with a command named `case`, it will conflict with the core `case` command:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
class MyPlugin(XSOARPlugin):
|
|
259
|
+
def get_command(self):
|
|
260
|
+
@click.command()
|
|
261
|
+
def case(): # ❌ This conflicts with core 'case' command
|
|
262
|
+
click.echo("My case command")
|
|
263
|
+
return case
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Result**: The plugin loads successfully, but the `case` command remains the core command. The plugin's `case` command is ignored.
|
|
267
|
+
|
|
268
|
+
### Resolving Conflicts
|
|
269
|
+
|
|
270
|
+
**Option 1: Rename the command**
|
|
271
|
+
```python
|
|
272
|
+
class MyPlugin(XSOARPlugin):
|
|
273
|
+
def get_command(self):
|
|
274
|
+
@click.command()
|
|
275
|
+
def mycase(): # ✅ Unique name
|
|
276
|
+
click.echo("My case command")
|
|
277
|
+
return mycase
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Option 2: Use a command group**
|
|
281
|
+
```python
|
|
282
|
+
class MyPlugin(XSOARPlugin):
|
|
283
|
+
def get_command(self):
|
|
284
|
+
@click.group()
|
|
285
|
+
def myplugin():
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
@click.command()
|
|
289
|
+
def case(): # ✅ Namespaced as 'myplugin case'
|
|
290
|
+
click.echo("My case command")
|
|
291
|
+
|
|
292
|
+
myplugin.add_command(case)
|
|
293
|
+
return myplugin
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Core Commands to Avoid
|
|
297
|
+
|
|
298
|
+
These command names are reserved by the core CLI:
|
|
299
|
+
- `case` - Case/incident management
|
|
300
|
+
- `config` - Configuration management
|
|
301
|
+
- `graph` - Dependency graphs
|
|
302
|
+
- `manifest` - Manifest operations
|
|
303
|
+
- `pack` - Content pack operations
|
|
304
|
+
- `playbook` - Playbook operations
|
|
305
|
+
- `plugins` - Plugin management
|
|
306
|
+
|
|
307
|
+
## Best Practices
|
|
308
|
+
|
|
309
|
+
### 1. Plugin Naming
|
|
310
|
+
|
|
311
|
+
- Use descriptive names for your plugin class and command
|
|
312
|
+
- Avoid conflicts with existing commands
|
|
313
|
+
- Use lowercase with underscores for file names: `my_plugin.py`
|
|
314
|
+
|
|
315
|
+
### 2. Command Structure
|
|
316
|
+
|
|
317
|
+
- Use command groups for plugins with multiple commands
|
|
318
|
+
- Provide helpful descriptions and help text
|
|
319
|
+
- Use appropriate Click decorators for options and arguments
|
|
320
|
+
|
|
321
|
+
### 3. Error Handling
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
def get_command(self) -> click.Command:
|
|
325
|
+
@click.command()
|
|
326
|
+
def mycommand():
|
|
327
|
+
try:
|
|
328
|
+
# Your logic here
|
|
329
|
+
pass
|
|
330
|
+
except Exception as e:
|
|
331
|
+
click.echo(f"Error: {e}", err=True)
|
|
332
|
+
raise click.Abort()
|
|
333
|
+
|
|
334
|
+
return mycommand
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 4. Configuration Access
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
from xsoar_cli.utilities import load_config
|
|
341
|
+
|
|
342
|
+
@click.command()
|
|
343
|
+
@click.pass_context
|
|
344
|
+
@load_config
|
|
345
|
+
def my_command(ctx: click.Context):
|
|
346
|
+
# Access configuration
|
|
347
|
+
config = ctx.obj
|
|
348
|
+
# Access XSOAR clients
|
|
349
|
+
dev_client = ctx.obj["server_envs"]["dev"]
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### 5. Logging
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
import logging
|
|
356
|
+
|
|
357
|
+
logger = logging.getLogger(__name__)
|
|
358
|
+
|
|
359
|
+
class MyPlugin(XSOARPlugin):
|
|
360
|
+
def initialize(self):
|
|
361
|
+
logger.info("Plugin initialized")
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Troubleshooting
|
|
365
|
+
|
|
366
|
+
### Plugin Not Loading
|
|
367
|
+
|
|
368
|
+
1. **Check file location**: Ensure your plugin is in `~/.local/xsoar-cli/plugins/`
|
|
369
|
+
2. **Check syntax**: Make sure your Python syntax is correct
|
|
370
|
+
3. **Check inheritance**: Ensure your class inherits from `XSOARPlugin`
|
|
371
|
+
4. **Check required methods**: Implement all required properties and methods
|
|
372
|
+
|
|
373
|
+
### Debugging Plugins
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# Enable debug mode to see plugin loading details
|
|
377
|
+
xsoar-cli --debug plugins list
|
|
378
|
+
|
|
379
|
+
# Validate a specific plugin
|
|
380
|
+
xsoar-cli plugins validate
|
|
381
|
+
|
|
382
|
+
# Check for command conflicts
|
|
383
|
+
xsoar-cli plugins check-conflicts
|
|
384
|
+
|
|
385
|
+
# Check plugin info
|
|
386
|
+
xsoar-cli plugins info my_plugin
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Common Issues
|
|
390
|
+
|
|
391
|
+
1. **Import errors**: Make sure all required modules are installed
|
|
392
|
+
2. **Command conflicts**: Use `xsoar-cli plugins check-conflicts` to identify naming conflicts
|
|
393
|
+
3. **Missing methods**: Implement all required abstract methods
|
|
394
|
+
4. **Invalid return types**: Ensure `get_command()` returns a Click command
|
|
395
|
+
5. **Plugin not visible**: Check if your command conflicts with a core command
|
|
396
|
+
|
|
397
|
+
## Examples Repository
|
|
398
|
+
|
|
399
|
+
For more examples and inspiration, check out the community plugin examples at:
|
|
400
|
+
`~/.local/xsoar-cli/plugins/example_plugin.py` (created with `xsoar-cli plugins create-example`)
|
|
401
|
+
|
|
402
|
+
## Development Tips
|
|
403
|
+
|
|
404
|
+
### Testing Your Plugin
|
|
405
|
+
|
|
406
|
+
1. Create your plugin file
|
|
407
|
+
2. Test loading: `xsoar-cli plugins validate`
|
|
408
|
+
3. Test functionality: Use your commands
|
|
409
|
+
4. Reload after changes: `xsoar-cli plugins reload my_plugin`
|
|
410
|
+
|
|
411
|
+
### Using External Dependencies
|
|
412
|
+
|
|
413
|
+
If your plugin needs external packages, make sure they're installed in the same environment as xsoar-cli:
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
pip install my-required-package
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Then import them in your plugin:
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
try:
|
|
423
|
+
import my_required_package
|
|
424
|
+
except ImportError:
|
|
425
|
+
click.echo("Error: my-required-package is required for this plugin")
|
|
426
|
+
raise click.Abort()
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Plugin Distribution
|
|
430
|
+
|
|
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
|
+
|
|
433
|
+
For more complex plugins, consider packaging them as proper Python packages that install the plugin file automatically.
|
|
@@ -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."""
|