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.

@@ -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."""