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.
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__ = "1.0.9"
xsoar_cli/__init__.py ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,57 @@
1
+ # Case
2
+
3
+ Various case/incident related commands for XSOAR.
4
+
5
+ ## Get
6
+
7
+ Retrieve basic information about a single case. Returns raw JSON formatted with 4-space indentation.
8
+
9
+ **Syntax:** `xsoar-cli case get [OPTIONS] CASENUMBER`
10
+
11
+ **Options:**
12
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
13
+
14
+ **Examples:**
15
+ ```
16
+ xsoar-cli case get 312412
17
+ xsoar-cli case get --environment prod 312412
18
+ ```
19
+
20
+ ## Clone
21
+
22
+ Clone a case from one environment to another. Useful for copying production cases to development environment for testing.
23
+
24
+ **Syntax:** `xsoar-cli case clone [OPTIONS] CASENUMBER`
25
+
26
+ **Options:**
27
+ - `--source TEXT` - Source environment (default: prod)
28
+ - `--dest TEXT` - Destination environment (default: dev)
29
+
30
+ **Examples:**
31
+ ```
32
+ xsoar-cli case clone 312412 # Clone from prod to dev (defaults)
33
+ xsoar-cli case clone --source dev --dest prod 312412 # Clone from dev to prod
34
+ ```
35
+
36
+ ## Create
37
+
38
+ Create a new case in XSOAR with optional custom fields and case type.
39
+
40
+ **Syntax:** `xsoar-cli case create [OPTIONS] [NAME] [DETAILS]`
41
+
42
+ **Options:**
43
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
44
+ - `--casetype TEXT` - Case type (default: uses default case type from config)
45
+ - `--custom-fields TEXT` - Additional fields in format "field1=value1,field2=value2" (useful when XSOAR has mandatory custom case fields configured)
46
+ - `--custom-fields-delimiter TEXT` - Delimiter for custom fields (default: ",")
47
+
48
+ **Arguments:**
49
+ - `NAME` - Case title (default: "Test case created from xsoar-cli")
50
+ - `DETAILS` - Case description (default: "Placeholder case details")
51
+
52
+ **Examples:**
53
+ ```
54
+ xsoar-cli case create
55
+ xsoar-cli case create "Security Incident" "Suspicious network activity detected"
56
+ xsoar-cli case create --casetype "Phishing" --custom-fields "severity=High,source=Email" "Phishing Email" "Suspicious email received"
57
+ ```
File without changes
@@ -0,0 +1,130 @@
1
+ import json
2
+ from typing import TYPE_CHECKING
3
+
4
+ import click
5
+
6
+ from xsoar_cli.utilities import load_config, parse_string_to_dict, 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=None, help="Default environment set 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) -> None:
23
+ if not environment:
24
+ environment = ctx.obj["default_environment"]
25
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
26
+ response = xsoar_client.get_case(casenumber)
27
+ if response["total"] == 0 and not response["data"]:
28
+ click.echo(f"Cannot find case ID {casenumber}")
29
+ ctx.exit(1)
30
+ click.echo(json.dumps(response, indent=4))
31
+
32
+
33
+ @click.argument("casenumber", type=int)
34
+ @click.option("--source", default="prod", show_default=True, help="Source environment")
35
+ @click.option("--dest", default="dev", show_default=True, help="Destination environment")
36
+ @click.option(
37
+ "--custom-fields",
38
+ default=None,
39
+ help='Additional fields on the form "myfield=my_value,anotherfield=another value". Use machine name for field names, e.g mycustomfieldname.',
40
+ )
41
+ @click.option("--custom-fields-delimiter", default=",", help='Delimiter when specifying additional fields. Default is ","')
42
+ @click.command()
43
+ @click.pass_context
44
+ @load_config
45
+ def clone( # noqa: PLR0913
46
+ ctx: click.Context,
47
+ casenumber: int,
48
+ source: str,
49
+ dest: str,
50
+ custom_fields: str | None,
51
+ custom_fields_delimiter: str,
52
+ ) -> None:
53
+ """Clones a case from source to destination environment."""
54
+ valid_envs = validate_environments(source, dest, ctx=ctx)
55
+ if not valid_envs:
56
+ click.echo(f"Error: cannot find environments {source} and/or {dest} in config")
57
+ ctx.exit(1)
58
+ if custom_fields and "=" not in custom_fields:
59
+ click.echo('Malformed custom fields. Must be on the form "myfield=myvalue"')
60
+ ctx.exit(1)
61
+ xsoar_source_client: Client = ctx.obj["server_envs"][source]["xsoar_client"]
62
+ results = xsoar_source_client.get_case(casenumber)
63
+ data = results["data"][0]
64
+ # Dbot mirror info is irrelevant. This will be added again if applicable by XSOAR after ticket creation in dev.
65
+ data.pop("dbotMirrorId")
66
+ data.pop("dbotMirrorInstance")
67
+ data.pop("dbotMirrorDirection")
68
+ data.pop("dbotDirtyFields")
69
+ data.pop("dbotCurrentDirtyFields")
70
+ data.pop("dbotMirrorTags")
71
+ data.pop("dbotMirrorLastSync")
72
+ data.pop("id")
73
+ data.pop("created")
74
+ data.pop("modified")
75
+ # Ensure that playbooks run immediately when the case is created
76
+ data["createInvestigation"] = True
77
+ if "CustomFields" in data:
78
+ data["CustomFields"] = data["CustomFields"] | parse_string_to_dict(custom_fields, custom_fields_delimiter)
79
+
80
+ xsoar_dest_client: Client = ctx.obj["server_envs"][dest]["xsoar_client"]
81
+ case_data = xsoar_dest_client.create_case(data=data)
82
+ click.echo(json.dumps(case_data, indent=4))
83
+
84
+
85
+ @click.option("--environment", default=None, help="Default environment set in config file.")
86
+ @click.option("--casetype", default="", show_default=True, help="Create case of specified type. Default type set in config file.")
87
+ @click.option(
88
+ "--custom-fields",
89
+ default=None,
90
+ help='Additional fields on the form "myfield=my_value,anotherfield=another value". Use machine name for field names, e.g mycustomfieldname.',
91
+ )
92
+ @click.option("--custom-fields-delimiter", default=",", help='Delimiter when specifying additional fields. Default is ","')
93
+ @click.argument("details", type=str, default="Placeholder case details")
94
+ @click.argument("name", type=str, default="Test case created from xsoar-cli")
95
+ @click.command()
96
+ @click.pass_context
97
+ @load_config
98
+ def create( # noqa: PLR0913
99
+ ctx: click.Context,
100
+ environment: str | None,
101
+ casetype: str,
102
+ name: str,
103
+ custom_fields: str | None,
104
+ custom_fields_delimiter: str,
105
+ details: str,
106
+ ) -> None:
107
+ """Creates a new case in XSOAR. If invalid case type is specified as a command option, XSOAR will default to using Unclassified."""
108
+ if custom_fields and "=" not in custom_fields:
109
+ click.echo('Malformed custom fields. Must be on the form "myfield=myvalue"')
110
+ ctx.exit(1)
111
+ if not environment:
112
+ environment = ctx.obj["default_environment"]
113
+ xsoar_client: Client = ctx.obj["server_envs"][environment]["xsoar_client"]
114
+ if not casetype:
115
+ casetype = ctx.obj["default_new_case_type"]
116
+ data = {
117
+ "createInvestigation": True,
118
+ "name": name,
119
+ "type": casetype,
120
+ "details": details,
121
+ "CustomFields": parse_string_to_dict(custom_fields, custom_fields_delimiter),
122
+ }
123
+ case_data = xsoar_client.create_case(data=data)
124
+ case_id = case_data["id"]
125
+ click.echo(f"Created XSOAR case {case_id}")
126
+
127
+
128
+ case.add_command(get)
129
+ case.add_command(clone)
130
+ 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,64 @@
1
+ # Config
2
+
3
+ Configuration management commands for XSOAR CLI.
4
+
5
+ ## Create
6
+
7
+ Create a new configuration file based on a template. If the configuration file already exists, prompts for confirmation to overwrite.
8
+
9
+ **Syntax:** `xsoar-cli config create`
10
+
11
+ **Examples:**
12
+ ```
13
+ xsoar-cli config create
14
+ ```
15
+
16
+ ## Show
17
+
18
+ Display the current configuration file contents as formatted JSON. API keys are masked by default for security.
19
+
20
+ **Syntax:** `xsoar-cli config show [OPTIONS]`
21
+
22
+ **Options:**
23
+ - `--unmask` - Show unmasked API keys in output
24
+
25
+ **Examples:**
26
+ ```
27
+ xsoar-cli config show
28
+ xsoar-cli config show --unmask
29
+ ```
30
+
31
+ ## Validate
32
+
33
+ Validate that the configuration file is properly formatted JSON and test connectivity to each XSOAR environment defined in the configuration.
34
+
35
+ **Syntax:** `xsoar-cli config validate [OPTIONS]`
36
+
37
+ **Options:**
38
+ - `--only-test-environment TEXT` - Test connectivity for only the specified environment
39
+
40
+ **Examples:**
41
+ ```
42
+ xsoar-cli config validate
43
+ xsoar-cli config validate --only-test-environment prod
44
+ ```
45
+
46
+ ## Set Credentials
47
+
48
+ Update API credentials for a specific environment in the configuration file. Automatically sets server version based on whether a key ID is provided.
49
+
50
+ **Syntax:** `xsoar-cli config set-credentials [OPTIONS] APITOKEN`
51
+
52
+ **Options:**
53
+ - `--environment TEXT` - Target environment (default: dev)
54
+ - `--key_id INTEGER` - API key ID for XSOAR 8 (sets server_version to 8, omit for XSOAR 6)
55
+
56
+ **Arguments:**
57
+ - `APITOKEN` - The API token to set for the environment
58
+
59
+ **Examples:**
60
+ ```
61
+ xsoar-cli config set-credentials your-api-token-here
62
+ xsoar-cli config set-credentials --environment prod your-api-token-here
63
+ xsoar-cli config set-credentials --environment prod --key_id 123 your-api-token-here
64
+ ```
File without changes
@@ -0,0 +1,112 @@
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 (
12
+ get_config_file_contents,
13
+ get_config_file_path,
14
+ get_config_file_template_contents,
15
+ load_config,
16
+ )
17
+
18
+
19
+ @click.group(help="Create/validate etc")
20
+ def config() -> None:
21
+ pass
22
+
23
+
24
+ @click.command()
25
+ @click.option("--unmask", "masked", is_flag=True, default=False)
26
+ @click.pass_context
27
+ @load_config
28
+ def show(ctx: click.Context, masked: bool) -> None:
29
+ """Prints out current config. API keys are masked."""
30
+ config_file = get_config_file_path()
31
+ config = get_config_file_contents(config_file)
32
+ if not masked:
33
+ for key in config["server_config"]:
34
+ config["server_config"][key]["api_token"] = "MASKED" # noqa: S105
35
+ if "azure_storage_access_token" in config["server_config"][key]:
36
+ config["server_config"][key]["azure_storage_access_token"] = "*****" # noqa: S105
37
+
38
+ print(json.dumps(config, indent=4))
39
+ ctx.exit()
40
+
41
+
42
+ @click.command()
43
+ @click.option("--only-test-environment", default=None, show_default=True, help="Environment as defined in config file")
44
+ @click.option("--stacktrace", is_flag=True, default=False, help="Print full stack trace on config validation failure.")
45
+ @click.pass_context
46
+ @load_config
47
+ def validate(ctx: click.Context, only_test_environment: str, stacktrace: bool) -> None:
48
+ """Validates that the configuration file is JSON and tests connectivity for each XSOAR Client environment defined."""
49
+ return_code = 0
50
+ for server_env in ctx.obj["server_envs"]:
51
+ if only_test_environment and server_env != only_test_environment:
52
+ # Ignore environment if --only-test-environment option is given and environment does not match
53
+ # what the user specified in option
54
+ continue
55
+ click.echo(f'Testing "{server_env}" environment...', nl=False)
56
+ xsoar_client: Client = ctx.obj["server_envs"][server_env]["xsoar_client"]
57
+ try:
58
+ xsoar_client.test_connectivity()
59
+ except ConnectionError as ex:
60
+ if stacktrace:
61
+ raise ConnectionError from ex
62
+ click.echo("FAILED")
63
+ return_code = 1
64
+ continue
65
+ click.echo("OK")
66
+ if ctx.obj["default_environment"] not in ctx.obj["server_envs"]:
67
+ click.echo(f'Error: default environment "{ctx.obj["default_environment"]}" not found in server config.')
68
+ return_code = 1
69
+ ctx.exit(return_code)
70
+
71
+
72
+ @click.command()
73
+ def create() -> None:
74
+ """Create a new configuration file based on a template."""
75
+ # if confdir does not exist, create it
76
+ # if conf file does not exist, create it
77
+ config_file = get_config_file_path()
78
+ if config_file.is_file():
79
+ click.confirm(f"WARNING: {config_file} already exists. Overwrite?", abort=True)
80
+ config_file.parent.mkdir(exist_ok=True, parents=True)
81
+ config_data = get_config_file_template_contents()
82
+ config_file.write_text(json.dumps(config_data, indent=4))
83
+ click.echo(f"Wrote template configuration file {config_file}")
84
+
85
+
86
+ @click.option("--environment", default="dev", show_default=True, help="Environment as defined in config file")
87
+ @click.option("--key_id", type=int, help="If set then server config for server_version will be set to 8.")
88
+ @click.argument("apitoken", type=str)
89
+ @click.command()
90
+ @click.pass_context
91
+ @load_config
92
+ def set_credentials(ctx: click.Context, environment: str, apitoken: str, key_id: int) -> None: # noqa: ARG001
93
+ """Set individual credentials for an environment in the config file."""
94
+ config_file = get_config_file_path()
95
+ config_data = json.loads(config_file.read_text())
96
+ config_data["server_config"][environment]["api_token"] = apitoken
97
+ # If we are given an API key ID, then we know it's supposed to be used in XSOAR 8.
98
+ # Set or remove the xsiam_auth_id accordingly.
99
+ if key_id:
100
+ config_data["server_config"][environment]["xsiam_auth_id"] = key_id
101
+ config_data["server_config"][environment]["server_version"] = 8
102
+ else:
103
+ config_data["server_config"][environment]["server_version"] = 6
104
+ with contextlib.suppress(KeyError):
105
+ config_data["server_config"][environment].pop("xsiam_auth_id")
106
+ config_file.write_text(json.dumps(config_data, indent=4))
107
+
108
+
109
+ config.add_command(create)
110
+ config.add_command(validate)
111
+ config.add_command(show)
112
+ 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,122 @@
1
+ # Manifest
2
+
3
+ Content pack deployment management commands using a declarative configuration file (`xsoar_config.json`).
4
+
5
+ ## Generate
6
+
7
+ Generate a new manifest file from currently installed content packs. Assumes all packs are marketplace packs (no custom packs).
8
+
9
+ **Syntax:** `xsoar-cli manifest generate [OPTIONS] MANIFEST_PATH`
10
+
11
+ **Options:**
12
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
13
+
14
+ **Arguments:**
15
+ - `MANIFEST_PATH` - Path where the new manifest file will be created
16
+
17
+ **Examples:**
18
+ ```
19
+ xsoar-cli manifest generate ./xsoar_config.json
20
+ xsoar-cli manifest generate --environment prod ./xsoar_config.json
21
+ ```
22
+
23
+ ## Validate
24
+
25
+ Validate manifest JSON syntax and verify all specified content packs are available. Tests connectivity to pack sources and checks local pack metadata for development packs.
26
+
27
+ **Syntax:** `xsoar-cli manifest validate [OPTIONS] MANIFEST_PATH`
28
+
29
+ **Options:**
30
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
31
+
32
+ **Arguments:**
33
+ - `MANIFEST_PATH` - Path to the manifest file to validate
34
+
35
+ **Examples:**
36
+ ```
37
+ xsoar-cli manifest validate ./xsoar_config.json
38
+ xsoar-cli manifest validate --environment staging ./xsoar_config.json
39
+ ```
40
+
41
+ ## Update
42
+
43
+ Compare installed packs against available versions and update the manifest file with latest versions. Prompts for confirmation on each upgrade.
44
+
45
+ **Syntax:** `xsoar-cli manifest update [OPTIONS] MANIFEST_PATH`
46
+
47
+ **Options:**
48
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
49
+
50
+ **Arguments:**
51
+ - `MANIFEST_PATH` - Path to the manifest file to update
52
+
53
+ **Examples:**
54
+ ```
55
+ xsoar-cli manifest update ./xsoar_config.json
56
+ xsoar-cli manifest update --environment dev ./xsoar_config.json
57
+ ```
58
+
59
+ ## Diff
60
+
61
+ Compare the manifest definition against what is actually installed on the XSOAR server. Shows packs that are missing or have version mismatches.
62
+
63
+ **Syntax:** `xsoar-cli manifest diff [OPTIONS] MANIFEST_PATH`
64
+
65
+ **Options:**
66
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
67
+
68
+ **Arguments:**
69
+ - `MANIFEST_PATH` - Path to the manifest file to compare
70
+
71
+ **Examples:**
72
+ ```
73
+ xsoar-cli manifest diff ./xsoar_config.json
74
+ xsoar-cli manifest diff --environment prod ./xsoar_config.json
75
+ ```
76
+
77
+ ## Deploy
78
+
79
+ Install or update content packs on the XSOAR server according to the manifest. Only deploys packs that differ from current installation.
80
+
81
+ **Syntax:** `xsoar-cli manifest deploy [OPTIONS] MANIFEST_PATH`
82
+
83
+ **Options:**
84
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
85
+ - `--verbose` - Show detailed information about skipped packs
86
+ - `--yes` - Skip confirmation prompt
87
+
88
+ **Arguments:**
89
+ - `MANIFEST_PATH` - Path to the manifest file to deploy
90
+
91
+ **Examples:**
92
+ ```
93
+ xsoar-cli manifest deploy ./xsoar_config.json
94
+ xsoar-cli manifest deploy --environment prod --yes ./xsoar_config.json
95
+ xsoar-cli manifest deploy --verbose ./xsoar_config.json
96
+ ```
97
+
98
+ ## Manifest File Structure
99
+
100
+ The `xsoar_config.json` file defines content packs to be installed:
101
+
102
+ ```json
103
+ {
104
+ "custom_packs": [
105
+ {
106
+ "id": "MyCustomPack",
107
+ "version": "1.0.0",
108
+ "_comment": "Optional documentation comment"
109
+ }
110
+ ],
111
+ "marketplace_packs": [
112
+ {
113
+ "id": "CommonScripts",
114
+ "version": "1.20.0"
115
+ }
116
+ ]
117
+ }
118
+ ```
119
+
120
+ - **custom_packs**: Organization-developed packs stored in artifact repositories
121
+ - **marketplace_packs**: Official Palo Alto Networks content packs
122
+ - **_comment**: Optional field for documentation (preserved during updates)
File without changes