devservices 1.1.5__tar.gz → 1.1.6__tar.gz
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.
- {devservices-1.1.5 → devservices-1.1.6}/PKG-INFO +1 -1
- {devservices-1.1.5 → devservices-1.1.6}/README.md +1 -1
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/up.py +2 -1
- {devservices-1.1.5 → devservices-1.1.6}/devservices/main.py +0 -1
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/docker_compose.py +49 -11
- {devservices-1.1.5 → devservices-1.1.6}/devservices.egg-info/PKG-INFO +1 -1
- {devservices-1.1.5 → devservices-1.1.6}/pyproject.toml +1 -1
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_up.py +215 -0
- {devservices-1.1.5 → devservices-1.1.6}/LICENSE.md +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/__init__.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/__init__.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/down.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/list_dependencies.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/list_services.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/logs.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/purge.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/reset.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/serve.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/status.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/toggle.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/commands/update.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/configs/service_config.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/constants.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/exceptions.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/__init__.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/check_for_update.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/console.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/dependencies.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/devenv.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/docker.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/file_lock.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/git.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/install_binary.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/services.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/state.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices/utils/supervisor.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices.egg-info/SOURCES.txt +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices.egg-info/dependency_links.txt +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices.egg-info/entry_points.txt +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices.egg-info/requires.txt +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/devservices.egg-info/top_level.txt +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/setup.cfg +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/testing/__init__.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/testing/utils.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/__init__.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_down.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_list_dependencies.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_list_services.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_logs.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_purge.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_reset.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_serve.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_status.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_toggle.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/commands/test_update.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/configs/test_service_config.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/conftest.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/utils/test_check_for_update.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/utils/test_dependencies.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/utils/test_docker.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/utils/test_docker_compose.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/utils/test_git.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/utils/test_install_binary.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/utils/test_services.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/utils/test_state.py +0 -0
- {devservices-1.1.5 → devservices-1.1.6}/tests/utils/test_supervisor.py +0 -0
|
@@ -31,7 +31,7 @@ NOTE: service-name is an optional parameter. If not provided, devservices will a
|
|
|
31
31
|
The recommended way to install devservices is through a virtualenv in the requirements.txt. Once that is installed and a devservices config file is added, you should be able to run `devservices up` to begin local development.
|
|
32
32
|
|
|
33
33
|
```
|
|
34
|
-
devservices==1.1.
|
|
34
|
+
devservices==1.1.6
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
### 2. Add devservices config files
|
|
@@ -182,7 +182,7 @@ def up(args: Namespace, existing_status: Status | None = None) -> None:
|
|
|
182
182
|
def _pull_dependency_images(
|
|
183
183
|
cmd: DockerComposeCommand, current_env: dict[str, str], status: Status
|
|
184
184
|
) -> None:
|
|
185
|
-
run_cmd(cmd.full_command, current_env)
|
|
185
|
+
run_cmd(cmd.full_command, current_env, retries=4)
|
|
186
186
|
for dependency in cmd.services:
|
|
187
187
|
status.info(f"Pulled image for {dependency}")
|
|
188
188
|
|
|
@@ -224,6 +224,7 @@ def _up(
|
|
|
224
224
|
)
|
|
225
225
|
),
|
|
226
226
|
)
|
|
227
|
+
|
|
227
228
|
# Pull all images in parallel
|
|
228
229
|
status.info("Pulling images")
|
|
229
230
|
pull_commands = get_docker_compose_commands_to_run(
|
|
@@ -78,7 +78,6 @@ if not disable_sentry:
|
|
|
78
78
|
dsn="https://56470da7302c16e83141f62f88e46449@o1.ingest.us.sentry.io/4507946704961536",
|
|
79
79
|
traces_sample_rate=1.0,
|
|
80
80
|
profiles_sample_rate=1.0,
|
|
81
|
-
enable_tracing=True,
|
|
82
81
|
integrations=[ArgvIntegration()],
|
|
83
82
|
environment=sentry_environment,
|
|
84
83
|
before_send=before_send_error,
|
|
@@ -5,7 +5,9 @@ import logging
|
|
|
5
5
|
import os
|
|
6
6
|
import platform
|
|
7
7
|
import re
|
|
8
|
+
import shlex
|
|
8
9
|
import subprocess
|
|
10
|
+
import time
|
|
9
11
|
from typing import cast
|
|
10
12
|
from typing import NamedTuple
|
|
11
13
|
|
|
@@ -290,15 +292,51 @@ def get_docker_compose_commands_to_run(
|
|
|
290
292
|
return docker_compose_commands
|
|
291
293
|
|
|
292
294
|
|
|
293
|
-
def run_cmd(
|
|
295
|
+
def run_cmd(
|
|
296
|
+
cmd: list[str],
|
|
297
|
+
env: dict[str, str],
|
|
298
|
+
retries: int = 0,
|
|
299
|
+
retry_initial_wait: float = 5.0,
|
|
300
|
+
retry_exp: float = 2.0,
|
|
301
|
+
) -> subprocess.CompletedProcess[str]:
|
|
302
|
+
if retries < 0:
|
|
303
|
+
raise ValueError("Retries cannot be negative")
|
|
304
|
+
|
|
294
305
|
logger = logging.getLogger(LOGGER_NAME)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
console = Console()
|
|
307
|
+
cmd_pretty = shlex.join(cmd)
|
|
308
|
+
|
|
309
|
+
proc = None
|
|
310
|
+
retries += 1 # initial try
|
|
311
|
+
|
|
312
|
+
while retries > 0:
|
|
313
|
+
retries -= 1
|
|
314
|
+
try:
|
|
315
|
+
logger.debug(f"Running command: {cmd_pretty}")
|
|
316
|
+
proc = subprocess.run(
|
|
317
|
+
cmd, check=True, capture_output=True, text=True, env=env
|
|
318
|
+
)
|
|
319
|
+
return proc
|
|
320
|
+
except subprocess.CalledProcessError as e:
|
|
321
|
+
err = DockerComposeError(
|
|
322
|
+
command=cmd_pretty,
|
|
323
|
+
returncode=e.returncode,
|
|
324
|
+
stdout=e.stdout,
|
|
325
|
+
stderr=e.stderr,
|
|
326
|
+
)
|
|
327
|
+
if retries == 0:
|
|
328
|
+
raise err
|
|
329
|
+
|
|
330
|
+
console.warning(
|
|
331
|
+
f"""
|
|
332
|
+
Error: {err}
|
|
333
|
+
|
|
334
|
+
Retrying in {retry_initial_wait}s ({retries} retries left)...
|
|
335
|
+
"""
|
|
336
|
+
)
|
|
337
|
+
time.sleep(retry_initial_wait)
|
|
338
|
+
retry_initial_wait *= retry_exp
|
|
339
|
+
|
|
340
|
+
# make mypy happy
|
|
341
|
+
assert proc is not None
|
|
342
|
+
return proc
|
|
@@ -5,6 +5,7 @@ import subprocess
|
|
|
5
5
|
from argparse import Namespace
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from unittest import mock
|
|
8
|
+
from unittest.mock import call
|
|
8
9
|
|
|
9
10
|
import pytest
|
|
10
11
|
|
|
@@ -345,6 +346,220 @@ def test_up_error(
|
|
|
345
346
|
assert "Starting redis" not in captured.out.strip()
|
|
346
347
|
|
|
347
348
|
|
|
349
|
+
@mock.patch("time.sleep")
|
|
350
|
+
@mock.patch("devservices.utils.state.State.remove_service_entry")
|
|
351
|
+
@mock.patch("devservices.utils.state.State.update_service_entry")
|
|
352
|
+
@mock.patch("devservices.commands.up._create_devservices_network")
|
|
353
|
+
@mock.patch("devservices.commands.up.check_all_containers_healthy")
|
|
354
|
+
@mock.patch(
|
|
355
|
+
"devservices.utils.docker_compose.get_non_remote_services",
|
|
356
|
+
return_value={"clickhouse", "redis"},
|
|
357
|
+
)
|
|
358
|
+
def test_up_pull_error_timeout(
|
|
359
|
+
mock_get_non_remote_services: mock.Mock,
|
|
360
|
+
mock_check_all_containers_healthy: mock.Mock,
|
|
361
|
+
mock_create_devservices_network: mock.Mock,
|
|
362
|
+
mock_update_service_entry: mock.Mock,
|
|
363
|
+
mock_remove_service_entry: mock.Mock,
|
|
364
|
+
mock_sleep: mock.Mock,
|
|
365
|
+
capsys: pytest.CaptureFixture[str],
|
|
366
|
+
tmp_path: Path,
|
|
367
|
+
) -> None:
|
|
368
|
+
config = {
|
|
369
|
+
"x-sentry-service-config": {
|
|
370
|
+
"version": 0.1,
|
|
371
|
+
"service_name": "example-service",
|
|
372
|
+
"dependencies": {
|
|
373
|
+
"redis": {"description": "Redis"},
|
|
374
|
+
"clickhouse": {"description": "Clickhouse"},
|
|
375
|
+
},
|
|
376
|
+
"modes": {"default": ["redis", "clickhouse"]},
|
|
377
|
+
},
|
|
378
|
+
"services": {
|
|
379
|
+
"redis": {"image": "redis:6.2.14-alpine"},
|
|
380
|
+
"clickhouse": {
|
|
381
|
+
"image": "altinity/clickhouse-server:23.8.11.29.altinitystable"
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
create_config_file(tmp_path, config)
|
|
387
|
+
os.chdir(tmp_path)
|
|
388
|
+
|
|
389
|
+
args = Namespace(service_name=None, debug=False, mode="default")
|
|
390
|
+
|
|
391
|
+
with pytest.raises(SystemExit):
|
|
392
|
+
with mock.patch(
|
|
393
|
+
"devservices.utils.docker_compose.subprocess.run",
|
|
394
|
+
side_effect=[
|
|
395
|
+
subprocess.CalledProcessError(
|
|
396
|
+
returncode=1, output="", stderr="TLS handshake timeout", cmd=""
|
|
397
|
+
),
|
|
398
|
+
subprocess.CalledProcessError(
|
|
399
|
+
returncode=1, output="", stderr="TLS handshake timeout", cmd=""
|
|
400
|
+
),
|
|
401
|
+
subprocess.CalledProcessError(
|
|
402
|
+
returncode=1, output="", stderr="TLS handshake timeout", cmd=""
|
|
403
|
+
),
|
|
404
|
+
subprocess.CalledProcessError(
|
|
405
|
+
returncode=1, output="", stderr="TLS handshake timeout", cmd=""
|
|
406
|
+
),
|
|
407
|
+
subprocess.CalledProcessError(
|
|
408
|
+
returncode=1, output="", stderr="TLS handshake timeout", cmd=""
|
|
409
|
+
),
|
|
410
|
+
],
|
|
411
|
+
) as mock_subprocess_run:
|
|
412
|
+
up(args)
|
|
413
|
+
|
|
414
|
+
# assert multiple failed calls
|
|
415
|
+
assert (
|
|
416
|
+
mock_subprocess_run.mock_calls
|
|
417
|
+
== [
|
|
418
|
+
call(
|
|
419
|
+
[
|
|
420
|
+
"docker",
|
|
421
|
+
"compose",
|
|
422
|
+
"-p",
|
|
423
|
+
"example-service",
|
|
424
|
+
"-f",
|
|
425
|
+
f"{tmp_path}/{DEVSERVICES_DIR_NAME}/{CONFIG_FILE_NAME}",
|
|
426
|
+
"pull",
|
|
427
|
+
"clickhouse",
|
|
428
|
+
"redis",
|
|
429
|
+
],
|
|
430
|
+
check=True,
|
|
431
|
+
capture_output=True,
|
|
432
|
+
text=True,
|
|
433
|
+
env=mock.ANY,
|
|
434
|
+
)
|
|
435
|
+
]
|
|
436
|
+
# default is 4 retries (5 tries total)
|
|
437
|
+
* 5
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
mock_create_devservices_network.assert_called_once()
|
|
441
|
+
mock_check_all_containers_healthy.assert_not_called()
|
|
442
|
+
# Capture the printed output
|
|
443
|
+
captured = capsys.readouterr()
|
|
444
|
+
|
|
445
|
+
assert (
|
|
446
|
+
"Failed to start example-service: TLS handshake timeout" in captured.out.strip()
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@mock.patch("time.sleep")
|
|
451
|
+
@mock.patch("devservices.utils.state.State.remove_service_entry")
|
|
452
|
+
@mock.patch("devservices.utils.state.State.update_service_entry")
|
|
453
|
+
@mock.patch("devservices.commands.up._create_devservices_network")
|
|
454
|
+
@mock.patch("devservices.commands.up.check_all_containers_healthy")
|
|
455
|
+
@mock.patch(
|
|
456
|
+
"devservices.utils.docker_compose.get_non_remote_services",
|
|
457
|
+
return_value={"clickhouse", "redis"},
|
|
458
|
+
)
|
|
459
|
+
@mock.patch(
|
|
460
|
+
"devservices.commands.up.get_container_names_for_project",
|
|
461
|
+
return_value=["x", "y"],
|
|
462
|
+
)
|
|
463
|
+
def test_up_pull_error_eventual_success(
|
|
464
|
+
mock_get_container_names_for_project: mock.Mock,
|
|
465
|
+
mock_get_non_remote_services: mock.Mock,
|
|
466
|
+
mock_check_all_containers_healthy: mock.Mock,
|
|
467
|
+
mock_create_devservices_network: mock.Mock,
|
|
468
|
+
mock_update_service_entry: mock.Mock,
|
|
469
|
+
mock_remove_service_entry: mock.Mock,
|
|
470
|
+
mock_sleep: mock.Mock,
|
|
471
|
+
capsys: pytest.CaptureFixture[str],
|
|
472
|
+
tmp_path: Path,
|
|
473
|
+
) -> None:
|
|
474
|
+
config = {
|
|
475
|
+
"x-sentry-service-config": {
|
|
476
|
+
"version": 0.1,
|
|
477
|
+
"service_name": "example-service",
|
|
478
|
+
"dependencies": {
|
|
479
|
+
"redis": {"description": "Redis"},
|
|
480
|
+
"clickhouse": {"description": "Clickhouse"},
|
|
481
|
+
},
|
|
482
|
+
"modes": {"default": ["redis", "clickhouse"]},
|
|
483
|
+
},
|
|
484
|
+
"services": {
|
|
485
|
+
"redis": {"image": "redis:6.2.14-alpine"},
|
|
486
|
+
"clickhouse": {
|
|
487
|
+
"image": "altinity/clickhouse-server:23.8.11.29.altinitystable"
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
create_config_file(tmp_path, config)
|
|
493
|
+
os.chdir(tmp_path)
|
|
494
|
+
|
|
495
|
+
args = Namespace(service_name=None, debug=False, mode="default")
|
|
496
|
+
|
|
497
|
+
with mock.patch(
|
|
498
|
+
"devservices.utils.docker_compose.subprocess.run",
|
|
499
|
+
side_effect=[
|
|
500
|
+
subprocess.CalledProcessError(
|
|
501
|
+
returncode=1, output="", stderr="TLS handshake timeout", cmd=""
|
|
502
|
+
),
|
|
503
|
+
subprocess.CalledProcessError(
|
|
504
|
+
returncode=1, output="", stderr="TLS handshake timeout", cmd=""
|
|
505
|
+
),
|
|
506
|
+
subprocess.CompletedProcess(
|
|
507
|
+
args=(),
|
|
508
|
+
returncode=0,
|
|
509
|
+
),
|
|
510
|
+
subprocess.CompletedProcess(
|
|
511
|
+
args=(),
|
|
512
|
+
returncode=0,
|
|
513
|
+
),
|
|
514
|
+
],
|
|
515
|
+
) as mock_subprocess_run:
|
|
516
|
+
up(args)
|
|
517
|
+
|
|
518
|
+
assert mock_subprocess_run.mock_calls == [
|
|
519
|
+
call(
|
|
520
|
+
[
|
|
521
|
+
"docker",
|
|
522
|
+
"compose",
|
|
523
|
+
"-p",
|
|
524
|
+
"example-service",
|
|
525
|
+
"-f",
|
|
526
|
+
f"{tmp_path}/{DEVSERVICES_DIR_NAME}/{CONFIG_FILE_NAME}",
|
|
527
|
+
"pull",
|
|
528
|
+
"clickhouse",
|
|
529
|
+
"redis",
|
|
530
|
+
],
|
|
531
|
+
check=True,
|
|
532
|
+
capture_output=True,
|
|
533
|
+
text=True,
|
|
534
|
+
env=mock.ANY,
|
|
535
|
+
)
|
|
536
|
+
] * 3 + [
|
|
537
|
+
call(
|
|
538
|
+
[
|
|
539
|
+
"docker",
|
|
540
|
+
"compose",
|
|
541
|
+
"-p",
|
|
542
|
+
"example-service",
|
|
543
|
+
"-f",
|
|
544
|
+
f"{tmp_path}/{DEVSERVICES_DIR_NAME}/{CONFIG_FILE_NAME}",
|
|
545
|
+
"up",
|
|
546
|
+
"clickhouse",
|
|
547
|
+
"redis",
|
|
548
|
+
"-d",
|
|
549
|
+
],
|
|
550
|
+
check=True,
|
|
551
|
+
capture_output=True,
|
|
552
|
+
text=True,
|
|
553
|
+
env=mock.ANY,
|
|
554
|
+
)
|
|
555
|
+
]
|
|
556
|
+
|
|
557
|
+
mock_create_devservices_network.assert_called_once()
|
|
558
|
+
captured = capsys.readouterr()
|
|
559
|
+
|
|
560
|
+
assert "example-service started" in captured.out.strip()
|
|
561
|
+
|
|
562
|
+
|
|
348
563
|
@mock.patch("devservices.utils.state.State.remove_service_entry")
|
|
349
564
|
@mock.patch("devservices.utils.state.State.update_service_entry")
|
|
350
565
|
@mock.patch("devservices.commands.up._create_devservices_network")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|