aws-annoying 0.7.0__tar.gz → 0.8.0__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.
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.devcontainer/devcontainer.json +1 -3
- aws_annoying-0.8.0/.devcontainer/onCreateCommand.sh +3 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/PKG-INFO +1 -1
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/app.py +30 -1
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/ecs/task_definition_lifecycle.py +0 -3
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/logging_handler.py +3 -3
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/mfa/configure.py +13 -8
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/session_manager/install.py +6 -2
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/session_manager/port_forward.py +21 -11
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/session_manager/start.py +5 -1
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/session_manager/stop.py +9 -3
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/pyproject.toml +1 -1
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/_helpers/string_.py +8 -1
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/ecs/snapshots/test_task_definition_lifecycle/test_dry_run/stdout.txt +1 -1
- aws_annoying-0.8.0/tests/cli/mfa/snapshots/test_configure/test_basic/persist/stdout.txt +3 -0
- aws_annoying-0.8.0/tests/cli/mfa/snapshots/test_configure/test_basic/skip_persist/stdout.txt +3 -0
- aws_annoying-0.8.0/tests/cli/mfa/snapshots/test_configure/test_dry_run/stdout.txt +4 -0
- aws_annoying-0.8.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt +4 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/mfa/test_configure.py +50 -4
- aws_annoying-0.7.0/tests/session_manager/test_session_manager.py → aws_annoying-0.8.0/tests/cli/session_manager/test_install.py +3 -3
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/snapshots/test_load_variables/test_basic/stdout.txt +4 -4
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/snapshots/test_load_variables/test_env_prefix/stdout.txt +6 -6
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/snapshots/test_load_variables/test_overwrite_env/stdout.txt +6 -6
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/snapshots/test_load_variables/test_resource_not_found/ssm/stdout.txt +3 -3
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/snapshots/test_load_variables/test_unsupported_resource/stdout.txt +3 -3
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/uv.lock +1 -1
- aws_annoying-0.7.0/.devcontainer/onCreateCommand.sh +0 -1
- aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_basic/persist/stdout.txt +0 -3
- aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_basic/skip_persist/stdout.txt +0 -3
- aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt +0 -4
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.devcontainer/.env.example +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.devcontainer/Dockerfile +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.devcontainer/docker-compose.devcontainer.yaml +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.devcontainer/postAttachCommand.sh +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.editorconfig +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.gitattributes +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.github/dependabot.yaml +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.github/workflows/ci.yaml +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.github/workflows/release.yaml +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.gitignore +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.pre-commit-config.yaml +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.python-version +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.vscode/extensions.json +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.vscode/launch.json +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/.vscode/settings.json +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/LICENSE +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/Makefile +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/README.md +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/ecs/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/ecs/_app.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/ecs/wait_for_deployment.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/load_variables.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/main.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/mfa/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/mfa/_app.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/session_manager/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/session_manager/_app.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/cli/session_manager/_common.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/ecs/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/ecs/check.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/ecs/common.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/ecs/errors.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/ecs/wait_for.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/mfa_config.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/session_manager/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/session_manager/errors.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/session_manager/session_manager.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/session_manager/shortcuts.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/utils/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/utils/debugger.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/utils/downloader.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/utils/ec2.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/utils/platform.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/utils/timeout.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/aws_annoying/variable_loader.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/console/ecs-exec/README.md +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/console/ecs-exec/ecs-console.png +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/console/ecs-exec/ecs-exec.user.js +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/console/ecs-exec/session-manager.png +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/examples/dbeaver/README.md +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/examples/dbeaver/after-disconnect.png +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/examples/dbeaver/before-connect.png +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/examples/dbeaver/new-connection.png +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/examples/dbeaver/test-connection.png +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/_helpers.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/_helpers/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/_helpers/boto3.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/_helpers/command_builder.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/_helpers/invoke.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/_helpers/scripts/printenv.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/ecs/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/ecs/snapshots/test_task_definition_lifecycle/test_basic/stdout.txt +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/ecs/test_task_definition_lifecycle.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/ecs/test_wait_for_deployment.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/mfa/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/mfa/snapshots/test_configure/test_basic/persist/aws_config.ini +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/aws_config.ini +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/session_manager/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/session_manager/test_port_forward.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/session_manager/test_start.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/session_manager/test_stop.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/snapshots/test_load_variables/test_nothing/stdout.txt +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/snapshots/test_load_variables/test_replace_quiet/stdout.txt +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/test_app.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/test_load_variables.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/test_logging_handler.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/cli/test_main.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/conftest.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/ecs/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/ecs/test_check.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/ecs/test_common.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/ecs/test_errors.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/ecs/test_wait_for.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/session_manager/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/session_manager/test_errors.py +0 -0
- /aws_annoying-0.7.0/tests/cli/session_manager/test_install.py → /aws_annoying-0.8.0/tests/session_manager/test_session_manager.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/session_manager/test_shortcuts.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/test_mfa_config.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/test_variable_loader.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/utils/__init__.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/utils/test_downloader.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/utils/test_ec2.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/utils/test_platform.py +0 -0
- {aws_annoying-0.7.0 → aws_annoying-0.8.0}/tests/utils/test_timeout.py +0 -0
|
@@ -7,10 +7,8 @@
|
|
|
7
7
|
"overrideCommand": true,
|
|
8
8
|
"workspaceFolder": "/workspaces/aws-annoying",
|
|
9
9
|
"features": {
|
|
10
|
-
"ghcr.io/devcontainers-contrib/features/pre-commit:2": {},
|
|
11
10
|
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
|
|
12
|
-
"ghcr.io/devcontainers/features/aws-cli:1": {}
|
|
13
|
-
"ghcr.io/lasuillard/devcontainer-features/aws-ssm-session-manager-plugin:0.1": {}
|
|
11
|
+
"ghcr.io/devcontainers/features/aws-cli:1": {}
|
|
14
12
|
},
|
|
15
13
|
"onCreateCommand": "./.devcontainer/onCreateCommand.sh",
|
|
16
14
|
"postAttachCommand": "./.devcontainer/postAttachCommand.sh",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-annoying
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Utils to handle some annoying AWS tasks.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lasuillard/aws-annoying
|
|
6
6
|
Project-URL: Repository, https://github.com/lasuillard/aws-annoying.git
|
|
@@ -8,6 +8,10 @@ from typing import Optional
|
|
|
8
8
|
import typer
|
|
9
9
|
from rich import print # noqa: A004
|
|
10
10
|
from rich.console import Console
|
|
11
|
+
from rich.highlighter import ReprHighlighter
|
|
12
|
+
from rich.theme import Theme
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
11
15
|
|
|
12
16
|
app = typer.Typer(
|
|
13
17
|
pretty_exceptions_short=True,
|
|
@@ -51,7 +55,7 @@ def main( # noqa: D103
|
|
|
51
55
|
),
|
|
52
56
|
) -> None:
|
|
53
57
|
log_level = logging.DEBUG if verbose else logging.INFO
|
|
54
|
-
console =
|
|
58
|
+
console = _get_console()
|
|
55
59
|
logging_config: logging.config._DictConfigArgs = {
|
|
56
60
|
"version": 1,
|
|
57
61
|
"disable_existing_loggers": False,
|
|
@@ -89,3 +93,28 @@ def main( # noqa: D103
|
|
|
89
93
|
|
|
90
94
|
# Global flags
|
|
91
95
|
ctx.meta["dry_run"] = dry_run
|
|
96
|
+
if dry_run:
|
|
97
|
+
logger.warning("Dry run mode enabled. Some operation may behave differently to avoid making changes.")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _get_console() -> Console:
|
|
101
|
+
theme = Theme(
|
|
102
|
+
{
|
|
103
|
+
"repr.arn": "bold orange3",
|
|
104
|
+
"repr.constant": "bold blue",
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
return Console(soft_wrap=True, emoji=False, highlighter=CustomHighlighter(), theme=theme)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class CustomHighlighter(ReprHighlighter):
|
|
111
|
+
"""Custom highlighter to handle additional patterns."""
|
|
112
|
+
|
|
113
|
+
highlights = [ # noqa: RUF012
|
|
114
|
+
*ReprHighlighter.highlights,
|
|
115
|
+
# AWS Resource Name; https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html
|
|
116
|
+
# NOTE: Quite simplified regex, may not cover all cases.
|
|
117
|
+
r"(?P<arn>\barn:[0-9a-zA-Z/+=,\.@_\-:]+\b)",
|
|
118
|
+
# Constants
|
|
119
|
+
r"(?P<constant>\b[A-Z_]+\b)",
|
|
120
|
+
]
|
|
@@ -40,9 +40,6 @@ def task_definition_lifecycle(
|
|
|
40
40
|
) -> None:
|
|
41
41
|
"""Execute ECS task definition lifecycle."""
|
|
42
42
|
dry_run = ctx.meta["dry_run"]
|
|
43
|
-
if dry_run:
|
|
44
|
-
logger.info("Dry run mode enabled. Will not perform any actual changes.")
|
|
45
|
-
|
|
46
43
|
ecs = boto3.client("ecs")
|
|
47
44
|
|
|
48
45
|
# Get all task definitions for the family
|
|
@@ -15,10 +15,10 @@ class RichLogHandler(logging.Handler):
|
|
|
15
15
|
|
|
16
16
|
_level_emojis: Final[dict[str, str]] = {
|
|
17
17
|
"DEBUG": "🔍",
|
|
18
|
-
"INFO": "
|
|
18
|
+
"INFO": "🔔",
|
|
19
19
|
"WARNING": "⚠️",
|
|
20
|
-
"ERROR": "
|
|
21
|
-
"CRITICAL": "
|
|
20
|
+
"ERROR": "🚨",
|
|
21
|
+
"CRITICAL": "🔥",
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
def __init__(self, console: Console, *args: Any, **kwargs: Any) -> None:
|
|
@@ -17,6 +17,7 @@ logger = logging.getLogger(__name__)
|
|
|
17
17
|
|
|
18
18
|
@mfa_app.command()
|
|
19
19
|
def configure( # noqa: PLR0913
|
|
20
|
+
ctx: typer.Context,
|
|
20
21
|
*,
|
|
21
22
|
mfa_profile: Optional[str] = typer.Option(
|
|
22
23
|
None,
|
|
@@ -54,6 +55,8 @@ def configure( # noqa: PLR0913
|
|
|
54
55
|
),
|
|
55
56
|
) -> None:
|
|
56
57
|
"""Configure AWS profile for MFA."""
|
|
58
|
+
dry_run = ctx.meta["dry_run"]
|
|
59
|
+
|
|
57
60
|
# Expand user home directory
|
|
58
61
|
aws_credentials = aws_credentials.expanduser()
|
|
59
62
|
aws_config = aws_config.expanduser()
|
|
@@ -102,13 +105,14 @@ def configure( # noqa: PLR0913
|
|
|
102
105
|
mfa_profile,
|
|
103
106
|
aws_credentials,
|
|
104
107
|
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
if not dry_run:
|
|
109
|
+
update_credentials(
|
|
110
|
+
aws_credentials,
|
|
111
|
+
mfa_profile, # type: ignore[arg-type]
|
|
112
|
+
access_key=credentials["AccessKeyId"],
|
|
113
|
+
secret_key=credentials["SecretAccessKey"],
|
|
114
|
+
session_token=credentials["SessionToken"],
|
|
115
|
+
)
|
|
112
116
|
|
|
113
117
|
# Persist MFA configuration
|
|
114
118
|
if persist:
|
|
@@ -120,6 +124,7 @@ def configure( # noqa: PLR0913
|
|
|
120
124
|
mfa_config.mfa_profile = mfa_profile
|
|
121
125
|
mfa_config.mfa_source_profile = mfa_source_profile
|
|
122
126
|
mfa_config.mfa_serial_number = mfa_serial_number
|
|
123
|
-
|
|
127
|
+
if not dry_run:
|
|
128
|
+
mfa_config.save_ini_file(aws_config, aws_config_section)
|
|
124
129
|
else:
|
|
125
130
|
logger.warning("MFA configuration not persisted.")
|
|
@@ -15,12 +15,15 @@ logger = logging.getLogger(__name__)
|
|
|
15
15
|
# https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html
|
|
16
16
|
@session_manager_app.command()
|
|
17
17
|
def install(
|
|
18
|
-
|
|
18
|
+
ctx: typer.Context,
|
|
19
|
+
*,
|
|
20
|
+
yes: bool = typer.Option(
|
|
19
21
|
False, # noqa: FBT003
|
|
20
22
|
help="Do not ask confirmation for installation.",
|
|
21
23
|
),
|
|
22
24
|
) -> None:
|
|
23
25
|
"""Install AWS Session Manager plugin."""
|
|
26
|
+
dry_run = ctx.meta["dry_run"]
|
|
24
27
|
session_manager = SessionManager()
|
|
25
28
|
|
|
26
29
|
# Check session-manager-plugin already installed
|
|
@@ -31,7 +34,8 @@ def install(
|
|
|
31
34
|
|
|
32
35
|
# Install session-manager-plugin
|
|
33
36
|
logger.warning("Installing AWS Session Manager plugin. You could be prompted for admin privileges request.")
|
|
34
|
-
|
|
37
|
+
if not dry_run:
|
|
38
|
+
session_manager.install(confirm=yes, downloader=TQDMDownloader())
|
|
35
39
|
|
|
36
40
|
# Verify installation
|
|
37
41
|
is_installed, binary_path, version = session_manager.verify_installation()
|
|
@@ -19,6 +19,8 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
# https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html
|
|
20
20
|
@session_manager_app.command()
|
|
21
21
|
def port_forward( # noqa: PLR0913
|
|
22
|
+
ctx: typer.Context,
|
|
23
|
+
*,
|
|
22
24
|
# TODO(lasuillard): Add `--local-host` option, redirect the traffic to non-localhost bind (unsupported by AWS)
|
|
23
25
|
local_port: int = typer.Option(
|
|
24
26
|
...,
|
|
@@ -48,7 +50,7 @@ def port_forward( # noqa: PLR0913
|
|
|
48
50
|
"./session-manager-plugin.pid",
|
|
49
51
|
help="The path to the PID file to store the process ID of the session manager plugin.",
|
|
50
52
|
),
|
|
51
|
-
terminate_running_process: bool = typer.Option(
|
|
53
|
+
terminate_running_process: bool = typer.Option(
|
|
52
54
|
False, # noqa: FBT003
|
|
53
55
|
help="Terminate the process in the PID file if it already exists.",
|
|
54
56
|
),
|
|
@@ -58,6 +60,7 @@ def port_forward( # noqa: PLR0913
|
|
|
58
60
|
),
|
|
59
61
|
) -> None:
|
|
60
62
|
"""Start a port forwarding session using AWS Session Manager."""
|
|
63
|
+
dry_run = ctx.meta["dry_run"]
|
|
61
64
|
session_manager = SessionManager()
|
|
62
65
|
|
|
63
66
|
# Check if the PID file already exists
|
|
@@ -86,7 +89,7 @@ def port_forward( # noqa: PLR0913
|
|
|
86
89
|
logger.info("Instance ID resolved: [bold]%s[/bold]", instance_id)
|
|
87
90
|
target = instance_id
|
|
88
91
|
else:
|
|
89
|
-
logger.
|
|
92
|
+
logger.error("Instance with name '%s' not found.", through)
|
|
90
93
|
raise typer.Exit(1)
|
|
91
94
|
|
|
92
95
|
# Initiate the session
|
|
@@ -111,19 +114,26 @@ def port_forward( # noqa: PLR0913
|
|
|
111
114
|
through,
|
|
112
115
|
reason,
|
|
113
116
|
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
if not dry_run:
|
|
118
|
+
proc = subprocess.Popen( # noqa: S603
|
|
119
|
+
command,
|
|
120
|
+
stdout=stdout,
|
|
121
|
+
stderr=subprocess.STDOUT,
|
|
122
|
+
text=True,
|
|
123
|
+
close_fds=False, # FD inherited from parent process
|
|
124
|
+
)
|
|
125
|
+
pid = proc.pid
|
|
126
|
+
else:
|
|
127
|
+
pid = -1
|
|
128
|
+
|
|
121
129
|
logger.info(
|
|
122
130
|
"Session Manager Plugin started with PID %d. Outputs will be logged to %s.",
|
|
123
|
-
|
|
131
|
+
pid,
|
|
124
132
|
log_file.absolute(),
|
|
125
133
|
)
|
|
126
134
|
|
|
127
135
|
# Write the PID to the file
|
|
128
|
-
|
|
136
|
+
if not dry_run:
|
|
137
|
+
pid_file.write_text(str(pid))
|
|
138
|
+
|
|
129
139
|
logger.info("PID file written to %s.", pid_file.absolute())
|
|
@@ -18,6 +18,8 @@ logger = logging.getLogger(__name__)
|
|
|
18
18
|
|
|
19
19
|
@session_manager_app.command()
|
|
20
20
|
def start(
|
|
21
|
+
ctx: typer.Context,
|
|
22
|
+
*,
|
|
21
23
|
target: str = typer.Option(
|
|
22
24
|
...,
|
|
23
25
|
show_default=False,
|
|
@@ -29,6 +31,7 @@ def start(
|
|
|
29
31
|
),
|
|
30
32
|
) -> None:
|
|
31
33
|
"""Start new session."""
|
|
34
|
+
dry_run = ctx.meta["dry_run"]
|
|
32
35
|
session_manager = SessionManager()
|
|
33
36
|
|
|
34
37
|
# Resolve the instance name or ID
|
|
@@ -52,4 +55,5 @@ def start(
|
|
|
52
55
|
parameters={},
|
|
53
56
|
reason=reason,
|
|
54
57
|
)
|
|
55
|
-
|
|
58
|
+
if not dry_run:
|
|
59
|
+
os.execvp(command[0], command) # noqa: S606
|
|
@@ -14,16 +14,20 @@ logger = logging.getLogger(__name__)
|
|
|
14
14
|
|
|
15
15
|
@session_manager_app.command()
|
|
16
16
|
def stop(
|
|
17
|
+
ctx: typer.Context,
|
|
18
|
+
*,
|
|
17
19
|
pid_file: Path = typer.Option( # noqa: B008
|
|
18
20
|
"./session-manager-plugin.pid",
|
|
19
21
|
help="The path to the PID file to store the process ID of the session manager plugin.",
|
|
20
22
|
),
|
|
21
|
-
remove: bool = typer.Option(
|
|
23
|
+
remove: bool = typer.Option(
|
|
22
24
|
True, # noqa: FBT003
|
|
23
25
|
help="Remove the PID file after stopping the session.",
|
|
24
26
|
),
|
|
25
27
|
) -> None:
|
|
26
28
|
"""Stop running session for PID file."""
|
|
29
|
+
dry_run = ctx.meta["dry_run"]
|
|
30
|
+
|
|
27
31
|
# Check if PID file exists
|
|
28
32
|
if not pid_file.is_file():
|
|
29
33
|
logger.error("PID file not found: %s", pid_file)
|
|
@@ -40,13 +44,15 @@ def stop(
|
|
|
40
44
|
# Send SIGTERM to the process
|
|
41
45
|
try:
|
|
42
46
|
logger.warning("Terminating running process with PID %d.", pid)
|
|
43
|
-
|
|
47
|
+
if not dry_run:
|
|
48
|
+
os.kill(pid, signal.SIGTERM)
|
|
44
49
|
except ProcessLookupError:
|
|
45
50
|
logger.warning("Tried to terminate process with PID %d but does not exist.", pid)
|
|
46
51
|
|
|
47
52
|
# Remove the PID file
|
|
48
53
|
if remove:
|
|
49
54
|
logger.info("Removed the PID file %s.", pid_file)
|
|
50
|
-
|
|
55
|
+
if not dry_run:
|
|
56
|
+
pid_file.unlink()
|
|
51
57
|
|
|
52
58
|
logger.info("Terminated the session successfully.")
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
def normalize_console_output(output: str) -> str:
|
|
6
|
+
def normalize_console_output(output: str, *, replace: dict[str, str] | None = None) -> str:
|
|
5
7
|
"""Normalize the console output for easier comparison."""
|
|
6
8
|
# Remove leading and trailing spaces
|
|
7
9
|
output = output.strip()
|
|
@@ -9,6 +11,11 @@ def normalize_console_output(output: str) -> str:
|
|
|
9
11
|
# Unwrap each line
|
|
10
12
|
output = re.sub(r"[ ]+\n", " ", output)
|
|
11
13
|
|
|
14
|
+
# Extra replacements; e.g. temporary paths that may vary
|
|
15
|
+
if replace:
|
|
16
|
+
for old, new in replace.items():
|
|
17
|
+
output = output.replace(old, new)
|
|
18
|
+
|
|
12
19
|
# Handle Windows path separator
|
|
13
20
|
output = output.replace("\\", "/")
|
|
14
21
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
⚠️ Dry run mode enabled. Some operation may behave differently to avoid making changes.
|
|
2
2
|
⚠️ Deregistering 15 task definitions...
|
|
3
3
|
⚠️ Deregistered task definition 'my-task:1'
|
|
4
4
|
⚠️ Deregistered task definition 'my-task:2'
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
⚠️ Dry run mode enabled. Some operation may behave differently to avoid making changes.
|
|
2
|
+
🔔 Retrieving MFA credentials using profile default
|
|
3
|
+
⚠️ Updating MFA profile (mfa) to AWS credentials (<tmp_path>/credentials)
|
|
4
|
+
🔔 Persisting MFA configuration in AWS config (<tmp_path>/config), in aws-annoying:mfa section.
|
aws_annoying-0.8.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
🔔 Loaded MFA configuration from AWS config (<tmp_path>/config).
|
|
2
|
+
🔔 Retrieving MFA credentials using profile default
|
|
3
|
+
⚠️ Updating MFA profile (mfa) to AWS credentials (<tmp_path>/credentials)
|
|
4
|
+
🔔 Persisting MFA configuration in AWS config (<tmp_path>/config), in aws-annoying:mfa section.
|
|
@@ -56,8 +56,10 @@ def test_basic(snapshot: Snapshot, tmp_path: Path, skip_persist: bool) -> None:
|
|
|
56
56
|
|
|
57
57
|
# Assert
|
|
58
58
|
assert result.exit_code == 0
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
snapshot.assert_match(
|
|
60
|
+
normalize_console_output(result.stdout, replace={str(tmp_path): "<tmp_path>"}),
|
|
61
|
+
"stdout.txt",
|
|
62
|
+
)
|
|
61
63
|
|
|
62
64
|
ini = ConfigParser()
|
|
63
65
|
ini.read(aws_credentials)
|
|
@@ -102,8 +104,10 @@ def test_load_existing_config(snapshot: Snapshot, tmp_path: Path) -> None:
|
|
|
102
104
|
|
|
103
105
|
# Assert
|
|
104
106
|
assert result.exit_code == 0
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
snapshot.assert_match(
|
|
108
|
+
normalize_console_output(result.stdout, replace={str(tmp_path): "<tmp_path>"}),
|
|
109
|
+
"stdout.txt",
|
|
110
|
+
)
|
|
107
111
|
|
|
108
112
|
ini = ConfigParser()
|
|
109
113
|
ini.read(aws_credentials)
|
|
@@ -114,3 +118,45 @@ def test_load_existing_config(snapshot: Snapshot, tmp_path: Path) -> None:
|
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
snapshot.assert_match(aws_config.read_text(), "aws_config.ini")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_dry_run(snapshot: Snapshot, tmp_path: Path) -> None:
|
|
124
|
+
"""If dry-run mode enabled, configuration shouldn't updated."""
|
|
125
|
+
# Arrange
|
|
126
|
+
mfa_profile = "mfa"
|
|
127
|
+
aws_credentials = tmp_path / "credentials"
|
|
128
|
+
aws_config = tmp_path / "config"
|
|
129
|
+
|
|
130
|
+
# Act
|
|
131
|
+
result = runner.invoke(
|
|
132
|
+
app,
|
|
133
|
+
[
|
|
134
|
+
"--dry-run",
|
|
135
|
+
"mfa",
|
|
136
|
+
"configure",
|
|
137
|
+
"--mfa-profile",
|
|
138
|
+
mfa_profile,
|
|
139
|
+
"--mfa-source-profile",
|
|
140
|
+
"default",
|
|
141
|
+
"--mfa-serial-number",
|
|
142
|
+
"1234567890",
|
|
143
|
+
"--mfa-token-code",
|
|
144
|
+
"123456",
|
|
145
|
+
"--aws-credentials",
|
|
146
|
+
str(aws_credentials),
|
|
147
|
+
"--aws-config",
|
|
148
|
+
str(aws_config),
|
|
149
|
+
],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Assert
|
|
153
|
+
assert result.exit_code == 0
|
|
154
|
+
snapshot.assert_match(
|
|
155
|
+
normalize_console_output(result.stdout, replace={str(tmp_path): "<tmp_path>"}),
|
|
156
|
+
"stdout.txt",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
ini = ConfigParser()
|
|
160
|
+
ini.read(aws_credentials)
|
|
161
|
+
assert mfa_profile not in ini
|
|
162
|
+
assert not aws_config.exists()
|
|
@@ -17,7 +17,7 @@ pytestmark = [
|
|
|
17
17
|
|
|
18
18
|
@skip_if_macos
|
|
19
19
|
@skip_if_windows
|
|
20
|
-
def
|
|
20
|
+
def test_install_linux() -> None:
|
|
21
21
|
# Arrange
|
|
22
22
|
session_manager = SessionManager()
|
|
23
23
|
assert session_manager.verify_installation() == (False, None, None)
|
|
@@ -35,7 +35,7 @@ def test_linux_session_manager_install() -> None:
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@run_if_macos
|
|
38
|
-
def
|
|
38
|
+
def test_install_macos() -> None:
|
|
39
39
|
# Arrange
|
|
40
40
|
session_manager = SessionManager()
|
|
41
41
|
assert session_manager.verify_installation() == (False, None, None)
|
|
@@ -53,7 +53,7 @@ def test_macos_session_manager_install() -> None:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
@run_if_windows
|
|
56
|
-
def
|
|
56
|
+
def test_install_windows() -> None:
|
|
57
57
|
# Arrange
|
|
58
58
|
session_manager = SessionManager()
|
|
59
59
|
assert session_manager.verify_installation() == (False, None, None)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
🔔 Summary:
|
|
2
2
|
┏━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
3
3
|
┃ Index ┃ ARN ┃
|
|
4
4
|
┡━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
5
5
|
│ 0 │ arn:aws:secretsmanager:us-east-1:000000000000:secret:my-app/django-… │
|
|
6
6
|
│ 1 │ arn:aws:ssm:us-east-1:000000000000:parameter/my-app/django-settings │
|
|
7
7
|
└───────┴──────────────────────────────────────────────────────────────────────┘
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
🔔 Retrieving variables from AWS resources...
|
|
9
|
+
🔔 Retrieved 1 secrets and 1 parameters.
|
|
10
|
+
🔔 Running the command: tests/cli/_helpers/scripts/printenv.py DJANGO_SETTINGS_MODULE DJANGO_SECRET_KEY DJANGO_DEBUG DJANGO_ALLOWED_HOSTS
|
|
11
11
|
DJANGO_SETTINGS_MODULE=config.settings.development
|
|
12
12
|
DJANGO_SECRET_KEY=my-secret-key
|
|
13
13
|
DJANGO_DEBUG=False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
🔔 Loading ARNs from environment variables with prefix: 'LOAD_AWS_CONFIG__'
|
|
2
|
+
🔔 Found 1 sources from environment variables.
|
|
3
|
+
🔔 Summary:
|
|
4
4
|
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
5
5
|
┃ Index ┃ ARN ┃
|
|
6
6
|
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
│ 1 │ arn:aws:ssm:us-east-1:000000000000:parameter/my-app/django-s… │
|
|
9
9
|
│ 900_override │ arn:aws:ssm:us-east-1:000000000000:parameter/my-app/override │
|
|
10
10
|
└──────────────┴───────────────────────────────────────────────────────────────┘
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
🔔 Retrieving variables from AWS resources...
|
|
12
|
+
🔔 Retrieved 1 secrets and 2 parameters.
|
|
13
|
+
🔔 Running the command: tests/cli/_helpers/scripts/printenv.py DJANGO_SETTINGS_MODULE DJANGO_SECRET_KEY DJANGO_DEBUG DJANGO_ALLOWED_HOSTS
|
|
14
14
|
DJANGO_SETTINGS_MODULE=config.settings.development
|
|
15
15
|
DJANGO_SECRET_KEY=my-secret-key
|
|
16
16
|
DJANGO_DEBUG=False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
🔔 Loading ARNs from environment variables with prefix: 'LOAD_AWS_CONFIG__'
|
|
2
|
+
🔔 Found 1 sources from environment variables.
|
|
3
|
+
🔔 Summary:
|
|
4
4
|
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
5
5
|
┃ Index ┃ ARN ┃
|
|
6
6
|
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
│ 1 │ arn:aws:ssm:us-east-1:000000000000:parameter/my-app/django-s… │
|
|
9
9
|
│ 900_override │ arn:aws:ssm:us-east-1:000000000000:parameter/my-app/override │
|
|
10
10
|
└──────────────┴───────────────────────────────────────────────────────────────┘
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
🔔 Retrieving variables from AWS resources...
|
|
12
|
+
🔔 Retrieved 1 secrets and 2 parameters.
|
|
13
|
+
🔔 Running the command: tests/cli/_helpers/scripts/printenv.py DJANGO_SETTINGS_MODULE DJANGO_SECRET_KEY DJANGO_DEBUG DJANGO_ALLOWED_HOSTS
|
|
14
14
|
DJANGO_SETTINGS_MODULE=config.settings.local
|
|
15
15
|
DJANGO_SECRET_KEY=my-secret-key
|
|
16
16
|
DJANGO_DEBUG=False
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
🔔 Summary:
|
|
2
2
|
┏━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
3
3
|
┃ Index ┃ ARN ┃
|
|
4
4
|
┡━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
│ 1 │ arn:aws:ssm:us-east-1:000000000000:parameter/my-app/django-settings │
|
|
7
7
|
│ 2 │ arn:aws:ssm:us-east-1:123456789012:parameter/unknown-parameter │
|
|
8
8
|
└───────┴──────────────────────────────────────────────────────────────────────┘
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
🔔 Retrieving variables from AWS resources...
|
|
10
|
+
🚨 Failed to load the variables: Failed to retrieve parameters: ['unknown-parameter']
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
🔔 Summary:
|
|
2
2
|
┏━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
3
3
|
┃ Index ┃ ARN ┃
|
|
4
4
|
┡━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
5
5
|
│ 0 │ arn:aws:s3:::my-bucket/my-object │
|
|
6
6
|
└───────┴──────────────────────────────────┘
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
🔔 Retrieving variables from AWS resources...
|
|
8
|
+
🚨 Failed to load the variables: Unsupported resource: 'arn:aws:s3:::my-bucket/my-object'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
ℹ️ Loaded MFA configuration from AWS config (<tmp_path>/config).
|
|
2
|
-
ℹ️ Retrieving MFA credentials using profile default
|
|
3
|
-
⚠️ Updating MFA profile (mfa) to AWS credentials (<tmp_path>/credentials)
|
|
4
|
-
ℹ️ Persisting MFA configuration in AWS config (<tmp_path>/config), in aws-annoying:mfa section.
|
|
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
|
|
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
|