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 ADDED
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2025-present Torbjørn Lium <torben@lium.org>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ __version__ = "0.0.3"
xsoar_cli/__init__.py ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,31 @@
1
+ # Case
2
+
3
+ **Various case/incident related commands**
4
+
5
+ ## Clone
6
+ It may be useful to clone a case from the production environment to the dev environment. This will of course require a configuration file
7
+ with API keys for both environments. The syntax is `xsoar-cli case clone --dest TEXT --source TEXT CASENUMBER` where default environments for `dest` and `source` are "dev" and "prod", respectively.
8
+
9
+ #### Example invocation
10
+ ```
11
+ xsoar-cli case clone --dest prod --source dev 312412 # explicitly using the default options
12
+ xsoar-cli case clone 312412 # shorthand for the same invocation as above
13
+ ```
14
+
15
+
16
+ ## Create
17
+ It may be useful to create a case in the dev environment. The syntax is `xsoar-cli case create [OPTIONS] [NAME] [DETAILS]` where NAME and
18
+ DETAILS are optional. If NAME and DETAILS are not filled in, then default values are used.
19
+ #### Example invocation
20
+ ```
21
+ xsoar-cli case create 312412
22
+ xsoar-cli case create 312412 "This is the name/title of the case" "This is some descriptive text on what the case is about."
23
+ ```
24
+
25
+
26
+ ## Get
27
+ It may be useful to quickly fetch some case data. The syntax is `xsoar-cli case clone [OPTIONS] CASENUMBER` and the command outputs raw JSON indented with 4 whitespaces to make the resulting output at least somewhat readable.
28
+ #### Example invocation
29
+ ```
30
+ xsoar-cli case get 312412
31
+ ```
File without changes
@@ -0,0 +1,86 @@
1
+ import json
2
+ from typing import TYPE_CHECKING
3
+
4
+ import click
5
+
6
+ from xsoar_cli.utilities import load_config, validate_environments
7
+
8
+ if TYPE_CHECKING:
9
+ from xsoar_client.xsoar_client import Client
10
+
11
+
12
+ @click.group(help="Add new or modify existing XSOAR case")
13
+ def case() -> None:
14
+ pass
15
+
16
+
17
+ @click.argument("casenumber", type=int)
18
+ @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
19
+ @click.command(help="Get basic information about a single case in XSOAR")
20
+ @click.pass_context
21
+ @load_config
22
+ def get(ctx: click.Context, casenumber: int, environment: str) -> None:
23
+ xsoar_client: Client = ctx.obj["server_envs"][environment]
24
+ response = xsoar_client.get_case(casenumber)
25
+ if response["total"] == 0 and not response["data"]:
26
+ click.echo(f"Cannot find case ID {casenumber}")
27
+ ctx.exit(1)
28
+ click.echo(json.dumps(response, indent=4))
29
+
30
+
31
+ @click.argument("casenumber", type=int)
32
+ @click.option("--source", default="prod", show_default=True, help="Source environment")
33
+ @click.option("--dest", default="dev", show_default=True, help="Destination environment")
34
+ @click.command()
35
+ @click.pass_context
36
+ @load_config
37
+ def clone(ctx: click.Context, casenumber: int, source: str, dest: str) -> None:
38
+ """Clones a case from source to destination environment."""
39
+ valid_envs = validate_environments(source, dest, ctx=ctx)
40
+ if not valid_envs:
41
+ click.echo(f"Error: cannot find environments {source} and/or {dest} in config")
42
+ ctx.exit(1)
43
+ xsoar_source_client: Client = ctx.obj["server_envs"][source]
44
+ results = xsoar_source_client.get_case(casenumber)
45
+ data = results["data"][0]
46
+ # Dbot mirror info is irrelevant. This will be added again if applicable by XSOAR after ticket creation in dev.
47
+ data.pop("dbotMirrorId")
48
+ data.pop("dbotMirrorInstance")
49
+ data.pop("dbotMirrorDirection")
50
+ data.pop("dbotDirtyFields")
51
+ data.pop("dbotCurrentDirtyFields")
52
+ data.pop("dbotMirrorTags")
53
+ data.pop("dbotMirrorLastSync")
54
+ data.pop("id")
55
+ data.pop("created")
56
+ data.pop("modified")
57
+ # Ensure that playbooks run immediately when the case is created
58
+ data["createInvestigation"] = True
59
+
60
+ xsoar_dest_client: Client = ctx.obj["server_envs"][dest]
61
+ case_data = xsoar_dest_client.create_case(data=data)
62
+ click.echo(json.dumps(case_data, indent=4))
63
+
64
+
65
+ @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
66
+ @click.argument("details", type=str, default="Placeholder case details")
67
+ @click.argument("name", type=str, default="Test case created from xsoar-cli")
68
+ @click.command(help="Creates a single new case in XSOAR")
69
+ @click.pass_context
70
+ @load_config
71
+ def create(ctx: click.Context, environment: str, name: str, details: str) -> None:
72
+ xsoar_client: Client = ctx.obj["server_envs"][environment]
73
+ data = {
74
+ "createInvestigation": True,
75
+ "name": name,
76
+ "type": "MyCaseType", # grab this from config maybe?
77
+ "details": details,
78
+ }
79
+ case_data = xsoar_client.create_case(data=data)
80
+ case_id = case_data["id"]
81
+ click.echo(f"Created XSOAR case {case_id}")
82
+
83
+
84
+ case.add_command(get)
85
+ case.add_command(clone)
86
+ case.add_command(create)
xsoar_cli/cli.py ADDED
@@ -0,0 +1,41 @@
1
+ import logging
2
+
3
+ import click
4
+
5
+ from .__about__ import __version__
6
+ from .case import commands as case_commands
7
+ from .config import commands as config_commands
8
+ from .graph import commands as graph_commands
9
+ from .manifest import commands as manifest_commands
10
+ from .pack import commands as pack_commands
11
+ from .playbook import commands as playbook_commands
12
+ from .plugins import commands as plugin_commands
13
+ from .plugins.manager import PluginManager
14
+
15
+
16
+ @click.group()
17
+ @click.pass_context
18
+ @click.version_option(__version__)
19
+ @click.option("--debug", is_flag=True, help="Enable debug logging")
20
+ def cli(ctx: click.Context, debug: bool) -> None:
21
+ """XSOAR CLI - Command line interface for XSOAR operations."""
22
+ if debug:
23
+ logging.basicConfig(level=logging.DEBUG)
24
+
25
+
26
+ cli.add_command(config_commands.config)
27
+ cli.add_command(case_commands.case)
28
+ cli.add_command(pack_commands.pack)
29
+ cli.add_command(manifest_commands.manifest)
30
+ cli.add_command(playbook_commands.playbook)
31
+ cli.add_command(graph_commands.graph)
32
+ cli.add_command(plugin_commands.plugins)
33
+
34
+ # Load and register plugins after all core commands are added
35
+ plugin_manager = PluginManager()
36
+ try:
37
+ plugin_manager.load_all_plugins(ignore_errors=True)
38
+ plugin_manager.register_plugin_commands(cli)
39
+ except Exception:
40
+ # Silently ignore plugin loading errors to not break the CLI
41
+ pass
@@ -0,0 +1,12 @@
1
+ # Config
2
+ At the moment, only a single config command is implemented
3
+
4
+ ## Create
5
+ Creates a new configuration file in `~/.config/xsoar-cli/config.json` based on a template. If the file already exists, then the user is prompted to overwrite
6
+ the existing file.
7
+
8
+ ## Get
9
+ Reads the current configuration file and prints it out as JSON indented with 4 whitespaces. API keys are masked from output.
10
+
11
+ ## Validate
12
+ Ensures that the configuration file is valid parsable JSON and that each server environment defined is reachable.
File without changes
@@ -0,0 +1,99 @@
1
+ import json
2
+ from typing import TYPE_CHECKING
3
+
4
+ import click
5
+
6
+ if TYPE_CHECKING:
7
+ from xsoar_client.xsoar_client import Client
8
+
9
+ import contextlib
10
+
11
+ from xsoar_cli.utilities import get_config_file_contents, get_config_file_path, get_config_file_template_contents, load_config
12
+
13
+
14
+ @click.group(help="Create/validate etc")
15
+ def config() -> None:
16
+ pass
17
+
18
+
19
+ @click.command()
20
+ @click.option("--unmask", "masked", is_flag=True, default=False)
21
+ @click.pass_context
22
+ @load_config
23
+ def show(ctx: click.Context, masked: bool) -> None:
24
+ """Prints out current config. API keys are masked."""
25
+ config_file = get_config_file_path()
26
+ config = get_config_file_contents(config_file)
27
+ if not masked:
28
+ for key in config["server_config"]:
29
+ config["server_config"][key]["api_token"] = "MASKED" # noqa: S105
30
+ print(json.dumps(config, indent=4))
31
+ ctx.exit()
32
+
33
+
34
+ @click.command()
35
+ @click.option("--only-test-environment", default=None, show_default=True, help="Environment as defined in config file")
36
+ @click.pass_context
37
+ @load_config
38
+ def validate(ctx: click.Context, only_test_environment: str) -> None:
39
+ """Validates that the configuration file is JSON and tests connectivity for each XSOAR Client environment defined."""
40
+ return_code = 0
41
+ for server_env in ctx.obj["server_envs"]:
42
+ if only_test_environment and server_env != only_test_environment:
43
+ # Ignore environment if --only-test-environment option is given and environment does not match
44
+ # what the user specified in option
45
+ continue
46
+ click.echo(f'Testing "{server_env}" environment...', nl=False)
47
+ xsoar_client: Client = ctx.obj["server_envs"][server_env]
48
+ try:
49
+ xsoar_client.test_connectivity()
50
+ except ConnectionError as ex:
51
+ raise RuntimeError from ex
52
+ click.echo("FAILED")
53
+ return_code = 1
54
+ continue
55
+ click.echo("OK")
56
+ ctx.exit(return_code)
57
+
58
+
59
+ @click.command()
60
+ def create() -> None:
61
+ """Create a new configuration file based on a template."""
62
+ # if confdir does not exist, create it
63
+ # if conf file does not exist, create it
64
+ config_file = get_config_file_path()
65
+ if config_file.is_file():
66
+ click.confirm(f"WARNING: {config_file} already exists. Overwrite?", abort=True)
67
+ config_file.parent.mkdir(exist_ok=True, parents=True)
68
+ config_data = get_config_file_template_contents()
69
+ config_file.write_text(json.dumps(config_data, indent=4))
70
+ click.echo(f"Wrote template configuration file {config_file}")
71
+
72
+
73
+ @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
74
+ @click.option("--key_id", type=int, help="If set then server config for server_version will be set to 8.")
75
+ @click.argument("apitoken", type=str)
76
+ @click.command()
77
+ @click.pass_context
78
+ @load_config
79
+ def set_credentials(ctx: click.Context, environment: str, apitoken: str, key_id: int) -> None: # noqa: ARG001
80
+ """Set individual credentials for an environment in the config file."""
81
+ config_file = get_config_file_path()
82
+ config_data = json.loads(config_file.read_text())
83
+ config_data["server_config"][environment]["api_token"] = apitoken
84
+ # If we are given an API key ID, then we know it's supposed to be used in XSOAR 8.
85
+ # Set or remove the xsiam_auth_id accordingly.
86
+ if key_id:
87
+ config_data["server_config"][environment]["xsiam_auth_id"] = key_id
88
+ config_data["server_config"][environment]["server_version"] = 8
89
+ else:
90
+ config_data["server_config"][environment]["server_version"] = 6
91
+ with contextlib.suppress(KeyError):
92
+ config_data["server_config"][environment].pop("xsiam_auth_id")
93
+ config_file.write_text(json.dumps(config_data, indent=4))
94
+
95
+
96
+ config.add_command(create)
97
+ config.add_command(validate)
98
+ config.add_command(show)
99
+ config.add_command(set_credentials)
@@ -0,0 +1,17 @@
1
+ # Graph
2
+ BETA command. Under active development.
3
+
4
+ ## Generate
5
+ Creates a graph representation of the entire content repository and plots connected components. The syntax is `xsoar-cli graph generate [OPTIONS] [PACKS]...`
6
+ where the only required option is the path to the content repository. An optional PACKS argument can be specified with the paths of one or more content packs
7
+ you want to be plotted.
8
+
9
+ ### Example invocations
10
+ ```
11
+ # Plot the connected components from the graph of the entire content repository
12
+ xsoar-cli graph generate -rp ./content_repo
13
+ # Plot only the components found in MyFirstPack
14
+ xsoar-cli graph generate -rp ./content_repo ./content_repo/Packs/MyFirstPack
15
+ # Plot only connected components from MyFirstPack and MySecondPack
16
+ xsoar-cli graph generate -rp ./content_repo ./content_repo/Packs/MyFirstPack ./content_repo/Packs/MySecondPack
17
+ ```
File without changes
@@ -0,0 +1,32 @@
1
+ from pathlib import Path
2
+
3
+ import click
4
+ from xsoar_dependency_graph.xsoar_dependency_graph import ContentGraph
5
+
6
+
7
+ @click.group(help="(BETA) Create dependency graphs from one or more content packs")
8
+ def graph() -> None:
9
+ pass
10
+
11
+
12
+ @click.option("-rp", "--repo-path", required=True, type=click.Path(exists=True), help="Path to content repository")
13
+ @click.argument("packs", nargs=-1, required=False, type=click.Path(exists=True))
14
+ @click.command()
15
+ def generate(packs: tuple[Path], repo_path: Path) -> None:
16
+ """BETA
17
+
18
+ Generates a XSOAR dependency graph for one or more content packs. If no packs are defined in the [PACKS] argument,
19
+ a dependency graph is created for all content packs in the content repository.
20
+
21
+ Usage examples:
22
+
23
+ xsoar-cli graph generate -rp . Packs/Pack_one Packs/Pack_two
24
+
25
+ xsoar-cli graph generate -rp ."""
26
+ cg: ContentGraph = ContentGraph(repo_path=Path(repo_path))
27
+ packs_list = [Path(item) for item in packs]
28
+ cg.create_content_graph(pack_paths=packs_list)
29
+ cg.plot_connected_components()
30
+
31
+
32
+ graph.add_command(generate)
@@ -0,0 +1,23 @@
1
+ # Manifest
2
+
3
+ ## Validate
4
+ Verifies that the xsoar_config.json manifest is valid JSON. Does a HTTP HEAD calls to every upstream content pack defined. Checks
5
+ custom content pack availability in AWS S3 artefact repository.
6
+ ```
7
+ xsoar-cli manifest validate /my/full/path/to/xsoar_config.json
8
+ xsoar-cli manifest validate relative/path/to/xsoar_config.json
9
+ ```
10
+
11
+ ## Update
12
+ Updates and prompts the user to write the xsoar_config.json manifest with the latest available content packs. Both upstream and custom content packs are checked.
13
+ ```
14
+ xsoar-cli manifest update /my/full/path/to/xsoar_config.json
15
+ xsoar-cli manifest update relative/path/to/xsoar_config.json
16
+ ```
17
+
18
+ ## Diff
19
+ Queries the XSOAR server for installed packages and compares them to what is defined in the manifest. Prints out the diff.
20
+ ```
21
+ xsoar-cli manifest diff /my/full/path/to/xsoar_config.json
22
+ xsoar-cli manifest diff relative/path/to/xsoar_config.json
23
+ ```
File without changes
@@ -0,0 +1,200 @@
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="dev", show_default=True, help="Environment as defined in config file")
43
+ @click.argument("manifest", type=str)
44
+ @click.command()
45
+ @click.pass_context
46
+ @load_config
47
+ def update(ctx: click.Context, environment: str, manifest: str) -> None:
48
+ """Update manifest on disk with latest available content pack versions."""
49
+ xsoar_client: Client = ctx.obj["server_envs"][environment]
50
+ manifest_data = load_manifest(manifest)
51
+ click.echo("Fetching outdated packs from XSOAR server. This may take a minute...", nl=False)
52
+ results = xsoar_client.get_outdated_packs()
53
+ click.echo("done.")
54
+ if not results:
55
+ click.echo("No packs eligible for upgrade.")
56
+ sys.exit(0)
57
+
58
+ item1 = "Pack ID"
59
+ item2 = "Installed version"
60
+ item3 = "Latest available version"
61
+ header = f"{item1:50}{item2:20}{item3:20}"
62
+ click.echo(header)
63
+ for pack in results:
64
+ click.echo(f"{pack['id']:50}{pack['currentVersion']:20}{pack['latest']:20}")
65
+ click.echo(f"Total number of outdated content packs: {len(results)}")
66
+
67
+ for pack in results:
68
+ key = "custom_packs" if pack["author"] in ctx.obj["custom_pack_authors"] else "marketplace_packs"
69
+ index = next((i for i, item in enumerate(manifest_data[key]) if item["id"] == pack["id"]), None)
70
+ if index is None:
71
+ msg = f"Pack {pack['id']} not found in manifest."
72
+ click.echo(msg)
73
+ sys.exit(1)
74
+ comment = manifest_data[key][index].get("_comment", None)
75
+ if comment is not None:
76
+ print(f"WARNING: comment found in manifest for {pack['id']}: {comment}")
77
+ msg = f"Upgrade {pack['id']} from {pack['currentVersion']} to {pack['latest']}?"
78
+ should_upgrade = click.confirm(msg, default=True)
79
+ if should_upgrade:
80
+ manifest_data[key][index]["version"] = pack["latest"]
81
+ write_manifest(manifest, manifest_data)
82
+
83
+
84
+ @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
85
+ @click.argument("manifest", type=str)
86
+ @click.command()
87
+ @click.pass_context
88
+ @load_config
89
+ def validate(ctx: click.Context, environment: str, manifest: str) -> None:
90
+ """Validate manifest JSON and all pack availability. Validates upstream pack availability by doing HTTP CONNECT.
91
+ Custom pack availability is implementation dependant."""
92
+ xsoar_client: Client = ctx.obj["server_envs"][environment]
93
+ manifest_data = load_manifest(manifest)
94
+ click.echo("Manifest is valid JSON")
95
+ keys = ["custom_packs", "marketplace_packs"]
96
+ for key in keys:
97
+ custom = key == "custom_packs"
98
+ click.echo(f"Checking {key} availability ", nl=False)
99
+ for pack in manifest_data[key]:
100
+ if not xsoar_client.is_pack_available(pack_id=pack["id"], version=pack["version"], custom=custom):
101
+ # If we are in a merge request and the merge request contains a new pack as well
102
+ # as a updated xsoar_config.json manifest, then the pack is not available in S3
103
+ # before the MR is merged. Check if we can find the appropriate pack version locally
104
+ # If so, we can ignore this error because the pack will become available after merge.
105
+ manifest_arg = Path(manifest)
106
+ manifest_path = manifest_arg.resolve()
107
+ repo_path = manifest_path.parent
108
+ pack_metadata_path = Path(f"{repo_path}/Packs/{pack['id']}/pack_metadata.json")
109
+ with Path.open(pack_metadata_path, encoding="utf-8") as f:
110
+ pack_metadata = json.load(f)
111
+
112
+ if pack_metadata["currentVersion"] == pack["version"]:
113
+ continue
114
+ # The object key does not exist in AWS S3, and the relevant Pack locally does not have
115
+ # the requested version.
116
+ click.echo(f"\nFailed to reach pack {pack['id']} version {pack['version']}")
117
+ sys.exit(1)
118
+ click.echo(".", nl=False)
119
+ print()
120
+
121
+ click.echo("Manifest is valid JSON and all packs are reachable.")
122
+
123
+
124
+ @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
125
+ @click.argument("manifest", type=str)
126
+ @click.command()
127
+ @click.pass_context
128
+ @load_config
129
+ def diff(ctx: click.Context, manifest: str, environment: str) -> None:
130
+ """Prints out the differences (if any) between what is defined in the xsoar_config.json manifest and what is actually
131
+ installed on the XSOAR server."""
132
+ xsoar_client: Client = ctx.obj["server_envs"][environment]
133
+ manifest_data = load_manifest(manifest)
134
+ installed_packs = xsoar_client.get_installed_packs()
135
+ all_good = True
136
+ for key in manifest_data:
137
+ for pack in manifest_data[key]:
138
+ installed = next((item for item in installed_packs if item["id"] == pack["id"]), {})
139
+ if not installed:
140
+ click.echo(f"Pack {pack['id']} is not installed")
141
+ all_good = False
142
+ elif installed["currentVersion"] != pack["version"]:
143
+ msg = f"Manifest states {pack['id']} version {pack['version']} but version {installed['currentVersion']} is installed"
144
+ click.echo(msg)
145
+ all_good = False
146
+ if all_good:
147
+ click.echo("All packs up to date.")
148
+
149
+
150
+ @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
151
+ @click.option("--verbose", is_flag=True, default=False)
152
+ @click.option("--yes", is_flag=True, default=False)
153
+ @click.command()
154
+ @click.argument("manifest", type=str)
155
+ @click.pass_context
156
+ @load_config
157
+ def deploy(ctx: click.Context, environment: str, manifest: str, verbose: bool, yes: bool) -> None: # noqa: FBT001
158
+ """
159
+ Deploys content packs to the XSOAR server as defined in the xsoar_config.json manifest.
160
+ The PATH argument expects the full or relative path to xsoar_config.json
161
+
162
+ \b
163
+ Prompts for confirmation prior to pack installation.
164
+ """
165
+ should_continue = True
166
+ if not yes:
167
+ should_continue = click.confirm(
168
+ f"WARNING: this operation will attempt to deploy all packs defined in the manifest to XSOAR {environment} environment. Continue?",
169
+ )
170
+ if not should_continue:
171
+ ctx.exit()
172
+
173
+ xsoar_client: Client = ctx.obj["server_envs"][environment]
174
+ manifest_data = load_manifest(manifest)
175
+ click.echo("Fetching installed packs...", err=True)
176
+ installed_packs = xsoar_client.get_installed_packs()
177
+ click.echo("done.")
178
+ none_installed = True
179
+ for key in manifest_data:
180
+ custom = key == "custom_packs"
181
+ for pack in manifest_data[key]:
182
+ installed = next((item for item in installed_packs if item["id"] == pack["id"]), {})
183
+ if not installed or installed["currentVersion"] != pack["version"]:
184
+ # Install pack
185
+ click.echo(f"Installing {pack['id']} version {pack['version']}...", nl=False)
186
+ xsoar_client.deploy_pack(pack_id=pack["id"], pack_version=pack["version"], custom=custom)
187
+ click.echo("OK.")
188
+ none_installed = False
189
+ elif verbose:
190
+ click.echo(f"Not installing {pack['id']} version {pack['version']}. Already installed.")
191
+ # Print message that install is skipped
192
+
193
+ if none_installed:
194
+ click.echo("No packs to install. XSOAR server is up to date with manifest.")
195
+
196
+
197
+ manifest.add_command(deploy)
198
+ manifest.add_command(diff)
199
+ manifest.add_command(update)
200
+ manifest.add_command(validate)
@@ -0,0 +1,7 @@
1
+ # Pack
2
+
3
+ ## Delete
4
+
5
+ ## Deploy
6
+
7
+ ## Get outdated
File without changes
@@ -0,0 +1,55 @@
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="dev", show_default=True, help="Environment as defined 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, pack_id: str) -> None:
24
+ """Deletes a content pack from the XSOAR server."""
25
+ xsoar_client: Client = ctx.obj["server_envs"][environment]
26
+ if not xsoar_client.is_installed(pack_id=pack_id):
27
+ click.echo(f"Pack ID {pack_id} is not installed. Cannot delete.")
28
+ sys.exit(1)
29
+ xsoar_client.delete(pack_id=pack_id)
30
+ click.echo(f"Deleted pack {pack_id} from XSOAR {environment}")
31
+
32
+
33
+ @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
34
+ @click.command()
35
+ @click.pass_context
36
+ @load_config
37
+ def get_outdated(ctx: click.Context, environment: str) -> None:
38
+ """Prints out a list of outdated content packs."""
39
+ xsoar_client: Client = ctx.obj["server_envs"][environment]
40
+ click.echo("Fetching outdated packs. This may take a little while...", err=True)
41
+ outdated_packs = xsoar_client.get_outdated_packs()
42
+ if not outdated_packs:
43
+ click.echo("No outdated packs found")
44
+ sys.exit(0)
45
+ id_header = "Pack ID"
46
+ installed_header = "Installed version"
47
+ latest_header = "Latest version"
48
+ click.echo(f"{id_header:<50}{installed_header:>17}{latest_header:>17}")
49
+ for pack in outdated_packs:
50
+ msg = f"{pack['id']:<50}{pack['currentVersion']:>17}{pack['latest']:>17}"
51
+ click.echo(msg)
52
+
53
+
54
+ pack.add_command(delete)
55
+ pack.add_command(get_outdated)
@@ -0,0 +1,19 @@
1
+ # Playbook
2
+
3
+ ## Download
4
+ **This command requires a content repository-like directory structure in the current working directory**
5
+ It attempts to provide a smoother development experience when modifying playbooks by doing the following steps:
6
+ 1. download the playbook. Attempts to detect the proper content pack and outputs the file to $(cwd)/Packs/PackID/Playbooks/<playbook name>.yml
7
+ 2. run demisto-sdk format --assume-yes --no-validate --no-graph <playbook_name.yml>
8
+ 3. re-attach the downloaded playbook in XSOAR
9
+ Whitespace characters in Pack ID and Playbook name will be converted to underscores "_".
10
+
11
+ ### Current limitations
12
+ - This command only supports downloading playbooks which are already part of a content pack. Proper implementation on completely new
13
+ playbooks is yet to be implemented.
14
+ - Attempting to download a non-existing playbook will result in a 500 Internal Server Error
15
+
16
+ #### Example invocation:
17
+ ```
18
+ xsoar-cli playbook download "my awesome playbook.yml"
19
+ ```
File without changes