aws-annoying 0.5.0__tar.gz → 0.7.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.5.0 → aws_annoying-0.7.0}/.devcontainer/onCreateCommand.sh +0 -2
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/postAttachCommand.sh +2 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.github/workflows/ci.yaml +12 -8
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.github/workflows/release.yaml +1 -1
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.pre-commit-config.yaml +2 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.vscode/launch.json +9 -1
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.vscode/settings.json +2 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/PKG-INFO +48 -3
- aws_annoying-0.7.0/README.md +51 -0
- aws_annoying-0.7.0/aws_annoying/cli/app.py +91 -0
- aws_annoying-0.7.0/aws_annoying/cli/ecs/__init__.py +3 -0
- aws_annoying-0.7.0/aws_annoying/cli/ecs/_app.py +9 -0
- aws_annoying-0.5.0/aws_annoying/cli/ecs_task_definition_lifecycle.py → aws_annoying-0.7.0/aws_annoying/cli/ecs/task_definition_lifecycle.py +18 -13
- aws_annoying-0.7.0/aws_annoying/cli/ecs/wait_for_deployment.py +158 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/load_variables.py +20 -25
- aws_annoying-0.7.0/aws_annoying/cli/logging_handler.py +52 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/main.py +1 -1
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/mfa/configure.py +21 -12
- aws_annoying-0.7.0/aws_annoying/cli/session_manager/_common.py +23 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/install.py +8 -5
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/port_forward.py +22 -12
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/start.py +13 -5
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/stop.py +9 -7
- aws_annoying-0.7.0/aws_annoying/ecs/__init__.py +25 -0
- aws_annoying-0.7.0/aws_annoying/ecs/check.py +39 -0
- aws_annoying-0.7.0/aws_annoying/ecs/common.py +8 -0
- aws_annoying-0.7.0/aws_annoying/ecs/errors.py +14 -0
- aws_annoying-0.7.0/aws_annoying/ecs/wait_for.py +190 -0
- aws_annoying-0.5.0/aws_annoying/mfa.py → aws_annoying-0.7.0/aws_annoying/mfa_config.py +7 -2
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/session_manager/session_manager.py +2 -4
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/session_manager/shortcuts.py +10 -6
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/utils/downloader.py +1 -8
- aws_annoying-0.7.0/aws_annoying/utils/ec2.py +33 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/utils/platform.py +11 -0
- aws_annoying-0.7.0/aws_annoying/utils/timeout.py +85 -0
- aws_annoying-0.5.0/aws_annoying/variables.py → aws_annoying-0.7.0/aws_annoying/variable_loader.py +11 -16
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/pyproject.toml +8 -8
- aws_annoying-0.7.0/tests/_helpers.py +16 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/__init__.py +10 -2
- aws_annoying-0.7.0/tests/cli/_helpers/boto3.py +37 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/string_.py +3 -0
- aws_annoying-0.7.0/tests/cli/ecs/snapshots/test_task_definition_lifecycle/test_basic/stdout.txt +16 -0
- aws_annoying-0.7.0/tests/cli/ecs/snapshots/test_task_definition_lifecycle/test_dry_run/stdout.txt +20 -0
- aws_annoying-0.5.0/tests/cli/test_ecs_task_definition_lifecycle.py → aws_annoying-0.7.0/tests/cli/ecs/test_task_definition_lifecycle.py +9 -5
- aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_basic/persist/stdout.txt +3 -0
- aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_basic/skip_persist/stdout.txt +3 -0
- aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt +4 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/mfa/test_configure.py +2 -3
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_basic/stdout.txt +4 -3
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_env_prefix/stdout.txt +6 -5
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_overwrite_env/stdout.txt +6 -5
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_resource_not_found/ssm/stdout.txt +3 -2
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_unsupported_resource/stdout.txt +3 -2
- aws_annoying-0.7.0/tests/cli/test_app.py +34 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/test_load_variables.py +37 -75
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/conftest.py +2 -0
- aws_annoying-0.7.0/tests/ecs/test_check.py +75 -0
- aws_annoying-0.7.0/tests/ecs/test_wait_for.py +347 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/session_manager/test_session_manager.py +26 -6
- aws_annoying-0.7.0/tests/test_mfa_config.py +0 -0
- aws_annoying-0.7.0/tests/test_variable_loader.py +0 -0
- aws_annoying-0.7.0/tests/utils/__init__.py +0 -0
- aws_annoying-0.7.0/tests/utils/test_downloader.py +0 -0
- aws_annoying-0.7.0/tests/utils/test_ec2.py +84 -0
- aws_annoying-0.7.0/tests/utils/test_platform.py +0 -0
- aws_annoying-0.7.0/tests/utils/test_timeout.py +43 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/uv.lock +1039 -1007
- aws_annoying-0.5.0/README.md +0 -8
- aws_annoying-0.5.0/aws_annoying/cli/app.py +0 -10
- aws_annoying-0.5.0/aws_annoying/cli/session_manager/_common.py +0 -54
- aws_annoying-0.5.0/tests/cli/mfa/snapshots/test_configure/test_basic/persist/stdout.txt +0 -3
- aws_annoying-0.5.0/tests/cli/mfa/snapshots/test_configure/test_basic/skip_persist/stdout.txt +0 -3
- aws_annoying-0.5.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt +0 -4
- aws_annoying-0.5.0/tests/cli/snapshots/test_ecs_task_definition_lifecycle/test_basic/stdout.txt +0 -16
- aws_annoying-0.5.0/tests/cli/snapshots/test_ecs_task_definition_lifecycle/test_dry_run/stdout.txt +0 -17
- aws_annoying-0.5.0/tests/cli/snapshots/test_load_variables/test_dry_run/stdout.txt +0 -17
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/.env.example +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/Dockerfile +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/devcontainer.json +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/docker-compose.devcontainer.yaml +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.editorconfig +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.gitattributes +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.github/dependabot.yaml +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.gitignore +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.python-version +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.vscode/extensions.json +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/LICENSE +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/Makefile +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/mfa/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/mfa/_app.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/_app.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/session_manager/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/session_manager/errors.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/utils/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/utils/debugger.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/console/ecs-exec/README.md +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/console/ecs-exec/ecs-console.png +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/console/ecs-exec/ecs-exec.user.js +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/console/ecs-exec/session-manager.png +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/README.md +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/after-disconnect.png +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/before-connect.png +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/new-connection.png +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/test-connection.png +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/command_builder.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/invoke.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/scripts/printenv.py +0 -0
- {aws_annoying-0.5.0/tests/cli/mfa → aws_annoying-0.7.0/tests/cli/ecs}/__init__.py +0 -0
- /aws_annoying-0.5.0/tests/cli/session_manager/__init__.py → /aws_annoying-0.7.0/tests/cli/ecs/test_wait_for_deployment.py +0 -0
- {aws_annoying-0.5.0/tests/session_manager → aws_annoying-0.7.0/tests/cli/mfa}/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/mfa/snapshots/test_configure/test_basic/persist/aws_config.ini +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/aws_config.ini +0 -0
- {aws_annoying-0.5.0/tests/utils → aws_annoying-0.7.0/tests/cli/session_manager}/__init__.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/session_manager/test_install.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/session_manager/test_port_forward.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/session_manager/test_start.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/session_manager/test_stop.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_nothing/stdout.txt +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_replace_quiet/stdout.txt +0 -0
- /aws_annoying-0.5.0/tests/cli/test_app.py → /aws_annoying-0.7.0/tests/cli/test_logging_handler.py +0 -0
- {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/test_main.py +0 -0
- /aws_annoying-0.5.0/tests/session_manager/test_errors.py → /aws_annoying-0.7.0/tests/ecs/__init__.py +0 -0
- /aws_annoying-0.5.0/tests/session_manager/test_shortcuts.py → /aws_annoying-0.7.0/tests/ecs/test_common.py +0 -0
- /aws_annoying-0.5.0/tests/test_mfa.py → /aws_annoying-0.7.0/tests/ecs/test_errors.py +0 -0
- /aws_annoying-0.5.0/tests/test_variables.py → /aws_annoying-0.7.0/tests/session_manager/__init__.py +0 -0
- /aws_annoying-0.5.0/tests/utils/test_downloader.py → /aws_annoying-0.7.0/tests/session_manager/test_errors.py +0 -0
- /aws_annoying-0.5.0/tests/utils/test_platform.py → /aws_annoying-0.7.0/tests/session_manager/test_shortcuts.py +0 -0
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
uses: actions/checkout@v4
|
|
21
21
|
|
|
22
22
|
- name: Set up uv
|
|
23
|
-
uses: astral-sh/setup-uv@
|
|
23
|
+
uses: astral-sh/setup-uv@v6
|
|
24
24
|
with:
|
|
25
25
|
version: latest
|
|
26
26
|
enable-cache: true
|
|
@@ -52,7 +52,7 @@ jobs:
|
|
|
52
52
|
uses: actions/checkout@v4
|
|
53
53
|
|
|
54
54
|
- name: Set up uv
|
|
55
|
-
uses: astral-sh/setup-uv@
|
|
55
|
+
uses: astral-sh/setup-uv@v6
|
|
56
56
|
with:
|
|
57
57
|
version: latest
|
|
58
58
|
enable-cache: true
|
|
@@ -66,24 +66,28 @@ jobs:
|
|
|
66
66
|
- name: Run tests (Ubuntu)
|
|
67
67
|
if: ${{ matrix.os == 'ubuntu-latest' }}
|
|
68
68
|
run: |
|
|
69
|
+
which session-manager-plugin || true
|
|
69
70
|
sudo apt-get remove --yes session-manager-plugin
|
|
70
71
|
uv run pytest
|
|
71
72
|
|
|
72
73
|
- name: Run tests (macOS)
|
|
73
74
|
if: ${{ matrix.os == 'macos-latest' }}
|
|
74
75
|
run: |
|
|
76
|
+
which session-manager-plugin || true
|
|
75
77
|
brew uninstall session-manager-plugin
|
|
76
|
-
uv run pytest -m
|
|
78
|
+
uv run pytest -m 'not docker'
|
|
77
79
|
|
|
78
80
|
- name: Run tests (Windows)
|
|
79
81
|
if: ${{ matrix.os == 'windows-latest' }}
|
|
80
82
|
run: |
|
|
81
|
-
|
|
82
|
-
-Uri "https://raw.githubusercontent.com/aws/session-manager-plugin/refs/heads/mainline/Tools/src/update/windows/uninstall.bat" `
|
|
83
|
-
-OutFile "uninstall.bat"
|
|
83
|
+
Get-Command session-manager-plugin | Out-String -Width 2000
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
$uri = 'https://s3.amazonaws.com/session-manager-downloads/plugin/latest/windows/SessionManagerPluginSetup.exe'
|
|
86
|
+
$outFile = 'C:\Windows\Temp\SessionManagerPluginSetup.exe'
|
|
87
|
+
Invoke-WebRequest -Uri "$uri" -OutFile "$outFile"
|
|
88
|
+
& "$outFile" /uninstall /quiet
|
|
89
|
+
|
|
90
|
+
uv run pytest -m 'not docker'
|
|
87
91
|
|
|
88
92
|
- name: Upload test results to Codecov
|
|
89
93
|
uses: codecov/codecov-action@v5
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
"program": "${file}",
|
|
9
9
|
"console": "integratedTerminal",
|
|
10
10
|
"justMyCode": true
|
|
11
|
-
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "Python: Application CLI",
|
|
14
|
+
"type": "debugpy",
|
|
15
|
+
"request": "launch",
|
|
16
|
+
"module": "aws_annoying.cli.main",
|
|
17
|
+
"console": "integratedTerminal",
|
|
18
|
+
"justMyCode": true
|
|
19
|
+
},
|
|
12
20
|
]
|
|
13
21
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-annoying
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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
|
|
@@ -16,19 +16,21 @@ Requires-Dist: tqdm<5,>=4
|
|
|
16
16
|
Requires-Dist: typer<1,>=0
|
|
17
17
|
Provides-Extra: dev
|
|
18
18
|
Requires-Dist: boto3-stubs[ec2,ecs,secretsmanager,ssm,sts]>=1.37.1; extra == 'dev'
|
|
19
|
-
Requires-Dist: mypy
|
|
19
|
+
Requires-Dist: mypy<1.17,>=1.15; extra == 'dev'
|
|
20
20
|
Requires-Dist: ruff<0.12.0,>=0.9.9; extra == 'dev'
|
|
21
21
|
Requires-Dist: types-requests>=2.31.0.6; extra == 'dev'
|
|
22
22
|
Provides-Extra: test
|
|
23
23
|
Requires-Dist: coverage<7.9,>=7.6; extra == 'test'
|
|
24
24
|
Requires-Dist: moto[ecs,secretsmanager,server,ssm]~=5.1.1; extra == 'test'
|
|
25
|
-
Requires-Dist: pytest-cov
|
|
25
|
+
Requires-Dist: pytest-cov<6.2,>=6.0; extra == 'test'
|
|
26
26
|
Requires-Dist: pytest-env~=1.1.1; extra == 'test'
|
|
27
27
|
Requires-Dist: pytest-snapshot>=0.9.0; extra == 'test'
|
|
28
28
|
Requires-Dist: pytest-sugar~=1.0.0; extra == 'test'
|
|
29
29
|
Requires-Dist: pytest-xdist>=3.6.1; extra == 'test'
|
|
30
30
|
Requires-Dist: pytest~=8.3.2; extra == 'test'
|
|
31
31
|
Requires-Dist: testcontainers[localstack]>=4.9.2; extra == 'test'
|
|
32
|
+
Requires-Dist: toml>=0.10.2; extra == 'test'
|
|
33
|
+
Requires-Dist: types-toml>=0.10.8.20240310; extra == 'test'
|
|
32
34
|
Description-Content-Type: text/markdown
|
|
33
35
|
|
|
34
36
|
# aws-annoying
|
|
@@ -39,3 +41,46 @@ Description-Content-Type: text/markdown
|
|
|
39
41
|

|
|
40
42
|
|
|
41
43
|
Utils to handle some annoying AWS tasks.
|
|
44
|
+
|
|
45
|
+
## ❓ About
|
|
46
|
+
|
|
47
|
+
This project aims to provide a set of utilities and examples to help with some annoying tasks when working with AWS.
|
|
48
|
+
|
|
49
|
+
Major directories of the project:
|
|
50
|
+
|
|
51
|
+
- **aws_annoying** Python package containing CLI and utility functions.
|
|
52
|
+
- **console** Utilities to help working with AWS Console.
|
|
53
|
+
- **examples** Examples of how to use the package.
|
|
54
|
+
|
|
55
|
+
## 🚀 Installation
|
|
56
|
+
|
|
57
|
+
It is recommended to use [pipx](https://pipx.pypa.io/stable/) to install `aws-annoying`:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
$ pipx install aws-annoying
|
|
61
|
+
$ aws-annoying --help
|
|
62
|
+
|
|
63
|
+
Usage: aws-annoying [OPTIONS] COMMAND [ARGS]...
|
|
64
|
+
|
|
65
|
+
...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Available commands:
|
|
69
|
+
|
|
70
|
+
- **ecs** ECS utilities.
|
|
71
|
+
- **task-definition-lifecycle** Help to manage ECS task definitions lifecycle.
|
|
72
|
+
- **wait-for-deployment** Wait for ECS deployment to complete.
|
|
73
|
+
- **load-variables** Wrapper command to run command with variables from AWS resources injected as environment variables.
|
|
74
|
+
- **mfa** Commands to manage MFA authentication.
|
|
75
|
+
- **configure** Configure AWS profile for MFA.
|
|
76
|
+
- **session-manager** AWS Session Manager CLI utilities.
|
|
77
|
+
- **install** Install AWS Session Manager plugin.
|
|
78
|
+
- **port-forward** Start a port forwarding session using AWS Session Manager.
|
|
79
|
+
- **start** Start new session.
|
|
80
|
+
- **stop** Stop running session for PID file.
|
|
81
|
+
|
|
82
|
+
Please refer to the CLI help for more information about the available commands and options.
|
|
83
|
+
|
|
84
|
+
## 💖 Contributing
|
|
85
|
+
|
|
86
|
+
Any feedback, suggestions or contributions are welcome! Feel free to open an issue or a pull request.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# aws-annoying
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://github.com/lasuillard/aws-annoying/actions/workflows/ci.yaml)
|
|
5
|
+
[](https://codecov.io/gh/lasuillard/aws-annoying)
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
Utils to handle some annoying AWS tasks.
|
|
9
|
+
|
|
10
|
+
## ❓ About
|
|
11
|
+
|
|
12
|
+
This project aims to provide a set of utilities and examples to help with some annoying tasks when working with AWS.
|
|
13
|
+
|
|
14
|
+
Major directories of the project:
|
|
15
|
+
|
|
16
|
+
- **aws_annoying** Python package containing CLI and utility functions.
|
|
17
|
+
- **console** Utilities to help working with AWS Console.
|
|
18
|
+
- **examples** Examples of how to use the package.
|
|
19
|
+
|
|
20
|
+
## 🚀 Installation
|
|
21
|
+
|
|
22
|
+
It is recommended to use [pipx](https://pipx.pypa.io/stable/) to install `aws-annoying`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
$ pipx install aws-annoying
|
|
26
|
+
$ aws-annoying --help
|
|
27
|
+
|
|
28
|
+
Usage: aws-annoying [OPTIONS] COMMAND [ARGS]...
|
|
29
|
+
|
|
30
|
+
...
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Available commands:
|
|
34
|
+
|
|
35
|
+
- **ecs** ECS utilities.
|
|
36
|
+
- **task-definition-lifecycle** Help to manage ECS task definitions lifecycle.
|
|
37
|
+
- **wait-for-deployment** Wait for ECS deployment to complete.
|
|
38
|
+
- **load-variables** Wrapper command to run command with variables from AWS resources injected as environment variables.
|
|
39
|
+
- **mfa** Commands to manage MFA authentication.
|
|
40
|
+
- **configure** Configure AWS profile for MFA.
|
|
41
|
+
- **session-manager** AWS Session Manager CLI utilities.
|
|
42
|
+
- **install** Install AWS Session Manager plugin.
|
|
43
|
+
- **port-forward** Start a port forwarding session using AWS Session Manager.
|
|
44
|
+
- **start** Start new session.
|
|
45
|
+
- **stop** Stop running session for PID file.
|
|
46
|
+
|
|
47
|
+
Please refer to the CLI help for more information about the available commands and options.
|
|
48
|
+
|
|
49
|
+
## 💖 Contributing
|
|
50
|
+
|
|
51
|
+
Any feedback, suggestions or contributions are welcome! Feel free to open an issue or a pull request.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import logging
|
|
5
|
+
import logging.config
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich import print # noqa: A004
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(
|
|
13
|
+
pretty_exceptions_short=True,
|
|
14
|
+
pretty_exceptions_show_locals=False,
|
|
15
|
+
rich_markup_mode="rich",
|
|
16
|
+
no_args_is_help=True,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def show_version(value: Optional[bool]) -> None:
|
|
21
|
+
"""Show the version of the application."""
|
|
22
|
+
if not value:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
print(importlib.metadata.version("aws-annoying"))
|
|
26
|
+
raise typer.Exit(0)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.callback()
|
|
30
|
+
def main( # noqa: D103
|
|
31
|
+
ctx: typer.Context,
|
|
32
|
+
*,
|
|
33
|
+
version: Optional[bool] = typer.Option( # noqa: ARG001
|
|
34
|
+
None,
|
|
35
|
+
"--version",
|
|
36
|
+
is_eager=True,
|
|
37
|
+
callback=show_version,
|
|
38
|
+
help="Show the version and exit.",
|
|
39
|
+
),
|
|
40
|
+
quiet: bool = typer.Option(
|
|
41
|
+
False, # noqa: FBT003
|
|
42
|
+
help="Disable outputs.",
|
|
43
|
+
),
|
|
44
|
+
verbose: bool = typer.Option(
|
|
45
|
+
False, # noqa: FBT003
|
|
46
|
+
help="Enable verbose outputs.",
|
|
47
|
+
),
|
|
48
|
+
dry_run: bool = typer.Option(
|
|
49
|
+
False, # noqa: FBT003
|
|
50
|
+
help="Enable dry-run mode. If enabled, certain commands will avoid making changes.",
|
|
51
|
+
),
|
|
52
|
+
) -> None:
|
|
53
|
+
log_level = logging.DEBUG if verbose else logging.INFO
|
|
54
|
+
console = Console(soft_wrap=True, emoji=False)
|
|
55
|
+
logging_config: logging.config._DictConfigArgs = {
|
|
56
|
+
"version": 1,
|
|
57
|
+
"disable_existing_loggers": False,
|
|
58
|
+
"formatters": {
|
|
59
|
+
"rich": {
|
|
60
|
+
"format": "%(message)s",
|
|
61
|
+
"datefmt": "[%X]",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"handlers": {
|
|
65
|
+
"null": {
|
|
66
|
+
"class": "logging.NullHandler",
|
|
67
|
+
},
|
|
68
|
+
"rich": {
|
|
69
|
+
"class": "aws_annoying.cli.logging_handler.RichLogHandler",
|
|
70
|
+
"formatter": "rich",
|
|
71
|
+
"console": console,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
"root": {
|
|
75
|
+
"handlers": ["null"],
|
|
76
|
+
},
|
|
77
|
+
"loggers": {
|
|
78
|
+
"aws_annoying": {
|
|
79
|
+
"level": log_level,
|
|
80
|
+
"handlers": ["rich"],
|
|
81
|
+
"propagate": True,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
if quiet:
|
|
86
|
+
logging_config["loggers"]["aws_annoying"]["level"] = logging.CRITICAL
|
|
87
|
+
|
|
88
|
+
logging.config.dictConfig(logging_config)
|
|
89
|
+
|
|
90
|
+
# Global flags
|
|
91
|
+
ctx.meta["dry_run"] = dry_run
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
import boto3
|
|
6
7
|
import typer
|
|
7
|
-
from rich import print # noqa: A004
|
|
8
8
|
|
|
9
|
-
from .
|
|
9
|
+
from ._app import ecs_app
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from collections.abc import Iterator
|
|
13
13
|
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
14
17
|
_DELETE_CHUNK_SIZE = 10
|
|
15
18
|
|
|
16
19
|
|
|
17
|
-
@
|
|
18
|
-
def
|
|
20
|
+
@ecs_app.command()
|
|
21
|
+
def task_definition_lifecycle(
|
|
22
|
+
ctx: typer.Context,
|
|
19
23
|
*,
|
|
20
24
|
family: str = typer.Option(
|
|
21
25
|
...,
|
|
@@ -33,14 +37,11 @@ def ecs_task_definition_lifecycle(
|
|
|
33
37
|
False, # noqa: FBT003
|
|
34
38
|
help="Delete the task definition after deregistering it.",
|
|
35
39
|
),
|
|
36
|
-
dry_run: bool = typer.Option(
|
|
37
|
-
False, # noqa: FBT003
|
|
38
|
-
help="Do not perform any changes, only show what would be done.",
|
|
39
|
-
),
|
|
40
40
|
) -> None:
|
|
41
41
|
"""Execute ECS task definition lifecycle."""
|
|
42
|
+
dry_run = ctx.meta["dry_run"]
|
|
42
43
|
if dry_run:
|
|
43
|
-
|
|
44
|
+
logger.info("Dry run mode enabled. Will not perform any actual changes.")
|
|
44
45
|
|
|
45
46
|
ecs = boto3.client("ecs")
|
|
46
47
|
|
|
@@ -59,23 +60,27 @@ def ecs_task_definition_lifecycle(
|
|
|
59
60
|
|
|
60
61
|
# Keep the latest N task definitions
|
|
61
62
|
expired_taskdef_arns = task_definition_arns[:-keep_latest]
|
|
62
|
-
|
|
63
|
+
logger.warning("Deregistering %d task definitions...", len(expired_taskdef_arns))
|
|
63
64
|
for arn in expired_taskdef_arns:
|
|
64
65
|
if not dry_run:
|
|
65
66
|
ecs.deregister_task_definition(taskDefinition=arn)
|
|
66
67
|
|
|
67
68
|
# ARN like: "arn:aws:ecs:<region>:<account-id>:task-definition/<family>:<revision>"
|
|
68
69
|
_, family_revision = arn.split(":task-definition/")
|
|
69
|
-
|
|
70
|
+
logger.warning("Deregistered task definition [yellow]%r[/yellow]", family_revision)
|
|
70
71
|
|
|
71
72
|
if delete and expired_taskdef_arns:
|
|
72
73
|
# Delete the expired task definitions in chunks due to API limitation
|
|
73
|
-
|
|
74
|
+
logger.warning(
|
|
75
|
+
"Deleting %d task definitions in chunks of size %d...",
|
|
76
|
+
len(expired_taskdef_arns),
|
|
77
|
+
_DELETE_CHUNK_SIZE,
|
|
78
|
+
)
|
|
74
79
|
for idx, chunk in enumerate(_chunker(expired_taskdef_arns, _DELETE_CHUNK_SIZE)):
|
|
75
80
|
if not dry_run:
|
|
76
81
|
ecs.delete_task_definitions(taskDefinitions=chunk)
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
logger.warning("Deleted %d task definitions in %d-th batch.", len(chunk), idx)
|
|
79
84
|
|
|
80
85
|
|
|
81
86
|
def _chunker(sequence: list, size: int) -> Iterator[list]:
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from aws_annoying.ecs import (
|
|
10
|
+
DeploymentFailedError,
|
|
11
|
+
ECSServiceRef,
|
|
12
|
+
ServiceTaskDefinitionAssertionError,
|
|
13
|
+
check_service_task_definition,
|
|
14
|
+
wait_for_deployment_complete,
|
|
15
|
+
wait_for_deployment_start,
|
|
16
|
+
wait_for_service_stability,
|
|
17
|
+
)
|
|
18
|
+
from aws_annoying.utils.timeout import OperationTimeoutError, Timeout
|
|
19
|
+
|
|
20
|
+
from ._app import ecs_app
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@ecs_app.command()
|
|
26
|
+
def wait_for_deployment( # noqa: PLR0913
|
|
27
|
+
*,
|
|
28
|
+
cluster: str = typer.Option(
|
|
29
|
+
...,
|
|
30
|
+
help="The name of the ECS cluster.",
|
|
31
|
+
show_default=False,
|
|
32
|
+
),
|
|
33
|
+
service: str = typer.Option(
|
|
34
|
+
...,
|
|
35
|
+
help="The name of the ECS service.",
|
|
36
|
+
show_default=False,
|
|
37
|
+
),
|
|
38
|
+
expected_task_definition: Optional[str] = typer.Option(
|
|
39
|
+
None,
|
|
40
|
+
help=(
|
|
41
|
+
"The service's task definition expected after deployment."
|
|
42
|
+
" If provided, it will be used to assert the service's task definition after deployment finished or timed out." # noqa: E501
|
|
43
|
+
),
|
|
44
|
+
show_default=False,
|
|
45
|
+
),
|
|
46
|
+
polling_interval: int = typer.Option(
|
|
47
|
+
5,
|
|
48
|
+
help="The interval between any polling attempts, in seconds.",
|
|
49
|
+
min=1,
|
|
50
|
+
),
|
|
51
|
+
timeout_seconds: Optional[int] = typer.Option(
|
|
52
|
+
None,
|
|
53
|
+
help=(
|
|
54
|
+
"The maximum time to wait for the deployment to complete, in seconds."
|
|
55
|
+
" If not provided, it will wait indefinitely."
|
|
56
|
+
),
|
|
57
|
+
min=1,
|
|
58
|
+
),
|
|
59
|
+
wait_for_start: bool = typer.Option(
|
|
60
|
+
True, # noqa: FBT003
|
|
61
|
+
help=(
|
|
62
|
+
"Whether to wait for the deployment to start."
|
|
63
|
+
" Because there could be no deployment right after the deploy,"
|
|
64
|
+
" this option will wait for a new deployment to start if no running deployment is found."
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
wait_for_stability: bool = typer.Option(
|
|
68
|
+
False, # noqa: FBT003
|
|
69
|
+
help="Whether to wait for the service to be stable after the deployment.",
|
|
70
|
+
),
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Wait for ECS deployment to complete."""
|
|
73
|
+
start = datetime.now(tz=timezone.utc)
|
|
74
|
+
try:
|
|
75
|
+
with Timeout(timeout_seconds):
|
|
76
|
+
_wait_for_deployment(
|
|
77
|
+
ECSServiceRef(cluster=cluster, service=service),
|
|
78
|
+
wait_for_start=wait_for_start,
|
|
79
|
+
polling_interval=polling_interval,
|
|
80
|
+
wait_for_stability=wait_for_stability,
|
|
81
|
+
expected_task_definition=expected_task_definition,
|
|
82
|
+
)
|
|
83
|
+
except OperationTimeoutError:
|
|
84
|
+
logger.error( # noqa: TRY400
|
|
85
|
+
"Timeout reached after %s seconds. The deployment may not have finished.",
|
|
86
|
+
timeout_seconds,
|
|
87
|
+
)
|
|
88
|
+
raise typer.Exit(1) from None
|
|
89
|
+
except DeploymentFailedError as err:
|
|
90
|
+
elapsed = datetime.now(tz=timezone.utc) - start
|
|
91
|
+
logger.error( # noqa: TRY400
|
|
92
|
+
"Deployment failed in [bold]%.2f[/bold] seconds with error: %s",
|
|
93
|
+
elapsed.total_seconds(),
|
|
94
|
+
err,
|
|
95
|
+
)
|
|
96
|
+
raise typer.Exit(1) from None
|
|
97
|
+
else:
|
|
98
|
+
elapsed = datetime.now(tz=timezone.utc) - start
|
|
99
|
+
logger.info(
|
|
100
|
+
"Deployment completed in [bold]%.2f[/bold] seconds.",
|
|
101
|
+
elapsed.total_seconds(),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _wait_for_deployment(
|
|
106
|
+
service_ref: ECSServiceRef,
|
|
107
|
+
*,
|
|
108
|
+
wait_for_start: bool,
|
|
109
|
+
polling_interval: int = 5,
|
|
110
|
+
wait_for_stability: bool,
|
|
111
|
+
expected_task_definition: str | None = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
# Find current deployment for the service
|
|
114
|
+
logger.info(
|
|
115
|
+
"Looking up running deployment for service %s",
|
|
116
|
+
service_ref.service,
|
|
117
|
+
)
|
|
118
|
+
latest_deployment_arn = wait_for_deployment_start(
|
|
119
|
+
service_ref,
|
|
120
|
+
wait_for_start=wait_for_start,
|
|
121
|
+
polling_interval=polling_interval,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Polling for the deployment to finish (successfully or unsuccessfully)
|
|
125
|
+
logger.info(
|
|
126
|
+
"Start waiting for deployment %s to finish.",
|
|
127
|
+
latest_deployment_arn,
|
|
128
|
+
)
|
|
129
|
+
ok, status = wait_for_deployment_complete(latest_deployment_arn, polling_interval=polling_interval)
|
|
130
|
+
if ok:
|
|
131
|
+
logger.info(
|
|
132
|
+
"Deployment succeeded with status %s",
|
|
133
|
+
status,
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
msg = f"Deployment failed with status: {status}"
|
|
137
|
+
raise DeploymentFailedError(msg)
|
|
138
|
+
|
|
139
|
+
# Wait for the service to be stable
|
|
140
|
+
if wait_for_stability:
|
|
141
|
+
logger.info(
|
|
142
|
+
"Start waiting for service %s to be stable.",
|
|
143
|
+
service_ref.service,
|
|
144
|
+
)
|
|
145
|
+
wait_for_service_stability(service_ref, polling_interval=polling_interval)
|
|
146
|
+
|
|
147
|
+
# Check if the service task definition matches the expected one
|
|
148
|
+
if expected_task_definition:
|
|
149
|
+
logger.info(
|
|
150
|
+
"Checking if the service task definition is the expected one: %s",
|
|
151
|
+
expected_task_definition,
|
|
152
|
+
)
|
|
153
|
+
ok, actual = check_service_task_definition(service_ref, expect=expected_task_definition)
|
|
154
|
+
if not ok:
|
|
155
|
+
msg = f"The service task definition is not the expected one; got: {actual!r}"
|
|
156
|
+
raise ServiceTaskDefinitionAssertionError(msg)
|
|
157
|
+
|
|
158
|
+
logger.info("The service task definition matches the expected one.")
|