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.
Files changed (132) hide show
  1. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/onCreateCommand.sh +0 -2
  2. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/postAttachCommand.sh +2 -0
  3. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.github/workflows/ci.yaml +12 -8
  4. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.github/workflows/release.yaml +1 -1
  5. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.pre-commit-config.yaml +2 -0
  6. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.vscode/launch.json +9 -1
  7. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.vscode/settings.json +2 -0
  8. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/PKG-INFO +48 -3
  9. aws_annoying-0.7.0/README.md +51 -0
  10. aws_annoying-0.7.0/aws_annoying/cli/app.py +91 -0
  11. aws_annoying-0.7.0/aws_annoying/cli/ecs/__init__.py +3 -0
  12. aws_annoying-0.7.0/aws_annoying/cli/ecs/_app.py +9 -0
  13. 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
  14. aws_annoying-0.7.0/aws_annoying/cli/ecs/wait_for_deployment.py +158 -0
  15. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/load_variables.py +20 -25
  16. aws_annoying-0.7.0/aws_annoying/cli/logging_handler.py +52 -0
  17. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/main.py +1 -1
  18. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/mfa/configure.py +21 -12
  19. aws_annoying-0.7.0/aws_annoying/cli/session_manager/_common.py +23 -0
  20. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/install.py +8 -5
  21. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/port_forward.py +22 -12
  22. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/start.py +13 -5
  23. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/stop.py +9 -7
  24. aws_annoying-0.7.0/aws_annoying/ecs/__init__.py +25 -0
  25. aws_annoying-0.7.0/aws_annoying/ecs/check.py +39 -0
  26. aws_annoying-0.7.0/aws_annoying/ecs/common.py +8 -0
  27. aws_annoying-0.7.0/aws_annoying/ecs/errors.py +14 -0
  28. aws_annoying-0.7.0/aws_annoying/ecs/wait_for.py +190 -0
  29. aws_annoying-0.5.0/aws_annoying/mfa.py → aws_annoying-0.7.0/aws_annoying/mfa_config.py +7 -2
  30. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/session_manager/session_manager.py +2 -4
  31. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/session_manager/shortcuts.py +10 -6
  32. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/utils/downloader.py +1 -8
  33. aws_annoying-0.7.0/aws_annoying/utils/ec2.py +33 -0
  34. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/utils/platform.py +11 -0
  35. aws_annoying-0.7.0/aws_annoying/utils/timeout.py +85 -0
  36. aws_annoying-0.5.0/aws_annoying/variables.py → aws_annoying-0.7.0/aws_annoying/variable_loader.py +11 -16
  37. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/pyproject.toml +8 -8
  38. aws_annoying-0.7.0/tests/_helpers.py +16 -0
  39. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/__init__.py +10 -2
  40. aws_annoying-0.7.0/tests/cli/_helpers/boto3.py +37 -0
  41. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/string_.py +3 -0
  42. aws_annoying-0.7.0/tests/cli/ecs/snapshots/test_task_definition_lifecycle/test_basic/stdout.txt +16 -0
  43. aws_annoying-0.7.0/tests/cli/ecs/snapshots/test_task_definition_lifecycle/test_dry_run/stdout.txt +20 -0
  44. 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
  45. aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_basic/persist/stdout.txt +3 -0
  46. aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_basic/skip_persist/stdout.txt +3 -0
  47. aws_annoying-0.7.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt +4 -0
  48. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/mfa/test_configure.py +2 -3
  49. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_basic/stdout.txt +4 -3
  50. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_env_prefix/stdout.txt +6 -5
  51. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_overwrite_env/stdout.txt +6 -5
  52. {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
  53. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_unsupported_resource/stdout.txt +3 -2
  54. aws_annoying-0.7.0/tests/cli/test_app.py +34 -0
  55. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/test_load_variables.py +37 -75
  56. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/conftest.py +2 -0
  57. aws_annoying-0.7.0/tests/ecs/test_check.py +75 -0
  58. aws_annoying-0.7.0/tests/ecs/test_wait_for.py +347 -0
  59. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/session_manager/test_session_manager.py +26 -6
  60. aws_annoying-0.7.0/tests/test_mfa_config.py +0 -0
  61. aws_annoying-0.7.0/tests/test_variable_loader.py +0 -0
  62. aws_annoying-0.7.0/tests/utils/__init__.py +0 -0
  63. aws_annoying-0.7.0/tests/utils/test_downloader.py +0 -0
  64. aws_annoying-0.7.0/tests/utils/test_ec2.py +84 -0
  65. aws_annoying-0.7.0/tests/utils/test_platform.py +0 -0
  66. aws_annoying-0.7.0/tests/utils/test_timeout.py +43 -0
  67. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/uv.lock +1039 -1007
  68. aws_annoying-0.5.0/README.md +0 -8
  69. aws_annoying-0.5.0/aws_annoying/cli/app.py +0 -10
  70. aws_annoying-0.5.0/aws_annoying/cli/session_manager/_common.py +0 -54
  71. aws_annoying-0.5.0/tests/cli/mfa/snapshots/test_configure/test_basic/persist/stdout.txt +0 -3
  72. aws_annoying-0.5.0/tests/cli/mfa/snapshots/test_configure/test_basic/skip_persist/stdout.txt +0 -3
  73. aws_annoying-0.5.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt +0 -4
  74. aws_annoying-0.5.0/tests/cli/snapshots/test_ecs_task_definition_lifecycle/test_basic/stdout.txt +0 -16
  75. aws_annoying-0.5.0/tests/cli/snapshots/test_ecs_task_definition_lifecycle/test_dry_run/stdout.txt +0 -17
  76. aws_annoying-0.5.0/tests/cli/snapshots/test_load_variables/test_dry_run/stdout.txt +0 -17
  77. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/.env.example +0 -0
  78. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/Dockerfile +0 -0
  79. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/devcontainer.json +0 -0
  80. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.devcontainer/docker-compose.devcontainer.yaml +0 -0
  81. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.editorconfig +0 -0
  82. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.gitattributes +0 -0
  83. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.github/dependabot.yaml +0 -0
  84. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.gitignore +0 -0
  85. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.python-version +0 -0
  86. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/.vscode/extensions.json +0 -0
  87. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/LICENSE +0 -0
  88. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/Makefile +0 -0
  89. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/__init__.py +0 -0
  90. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/__init__.py +0 -0
  91. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/mfa/__init__.py +0 -0
  92. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/mfa/_app.py +0 -0
  93. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/__init__.py +0 -0
  94. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/cli/session_manager/_app.py +0 -0
  95. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/session_manager/__init__.py +0 -0
  96. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/session_manager/errors.py +0 -0
  97. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/utils/__init__.py +0 -0
  98. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/aws_annoying/utils/debugger.py +0 -0
  99. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/console/ecs-exec/README.md +0 -0
  100. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/console/ecs-exec/ecs-console.png +0 -0
  101. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/console/ecs-exec/ecs-exec.user.js +0 -0
  102. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/console/ecs-exec/session-manager.png +0 -0
  103. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/README.md +0 -0
  104. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/after-disconnect.png +0 -0
  105. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/before-connect.png +0 -0
  106. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/new-connection.png +0 -0
  107. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/examples/dbeaver/test-connection.png +0 -0
  108. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/__init__.py +0 -0
  109. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/__init__.py +0 -0
  110. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/command_builder.py +0 -0
  111. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/invoke.py +0 -0
  112. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/_helpers/scripts/printenv.py +0 -0
  113. {aws_annoying-0.5.0/tests/cli/mfa → aws_annoying-0.7.0/tests/cli/ecs}/__init__.py +0 -0
  114. /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
  115. {aws_annoying-0.5.0/tests/session_manager → aws_annoying-0.7.0/tests/cli/mfa}/__init__.py +0 -0
  116. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/mfa/snapshots/test_configure/test_basic/persist/aws_config.ini +0 -0
  117. {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
  118. {aws_annoying-0.5.0/tests/utils → aws_annoying-0.7.0/tests/cli/session_manager}/__init__.py +0 -0
  119. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/session_manager/test_install.py +0 -0
  120. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/session_manager/test_port_forward.py +0 -0
  121. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/session_manager/test_start.py +0 -0
  122. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/session_manager/test_stop.py +0 -0
  123. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_nothing/stdout.txt +0 -0
  124. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/snapshots/test_load_variables/test_replace_quiet/stdout.txt +0 -0
  125. /aws_annoying-0.5.0/tests/cli/test_app.py → /aws_annoying-0.7.0/tests/cli/test_logging_handler.py +0 -0
  126. {aws_annoying-0.5.0 → aws_annoying-0.7.0}/tests/cli/test_main.py +0 -0
  127. /aws_annoying-0.5.0/tests/session_manager/test_errors.py → /aws_annoying-0.7.0/tests/ecs/__init__.py +0 -0
  128. /aws_annoying-0.5.0/tests/session_manager/test_shortcuts.py → /aws_annoying-0.7.0/tests/ecs/test_common.py +0 -0
  129. /aws_annoying-0.5.0/tests/test_mfa.py → /aws_annoying-0.7.0/tests/ecs/test_errors.py +0 -0
  130. /aws_annoying-0.5.0/tests/test_variables.py → /aws_annoying-0.7.0/tests/session_manager/__init__.py +0 -0
  131. /aws_annoying-0.5.0/tests/utils/test_downloader.py → /aws_annoying-0.7.0/tests/session_manager/test_errors.py +0 -0
  132. /aws_annoying-0.5.0/tests/utils/test_platform.py → /aws_annoying-0.7.0/tests/session_manager/test_shortcuts.py +0 -0
@@ -1,3 +1 @@
1
1
  #!/usr/bin/env bash
2
-
3
- make install
@@ -6,3 +6,5 @@ echo 'alias aa="uv run aws-annoying"' >> ~/.bashrc
6
6
  sudo chown --recursive "$(id --user):$(id --group)" ~
7
7
  sudo chmod --recursive 600 ~/.config/op ~/.aws
8
8
  sudo chmod --recursive u=rwX,g=,o= ~/.config/op ~/.aws
9
+
10
+ make install
@@ -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@v5
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@v5
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 macos
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
- Invoke-WebRequest `
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
- Start-Process "cmd.exe" -ArgumentList "/c uninstall.bat" -Wait
86
- uv run pytest -m windows
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
@@ -17,7 +17,7 @@ jobs:
17
17
  uses: actions/checkout@v4
18
18
 
19
19
  - name: Set up uv
20
- uses: astral-sh/setup-uv@v5
20
+ uses: astral-sh/setup-uv@v6
21
21
  with:
22
22
  version: latest
23
23
  enable-cache: true
@@ -28,6 +28,8 @@ repos:
28
28
  - "**/conftest.py"
29
29
  - --extend-exclude
30
30
  - "**/_*/**/*.py"
31
+ - --extend-exclude
32
+ - "**/_*.py"
31
33
  - id: preferred-suffix
32
34
  args: [--rename]
33
35
 
@@ -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
  }
@@ -10,7 +10,9 @@
10
10
  "cSpell.words": [
11
11
  "Deregistering",
12
12
  "localstack",
13
+ "pytestmark",
13
14
  "sessionmanagerplugin",
15
+ "Stubber",
14
16
  "Tampermonkey"
15
17
  ],
16
18
  "editor.formatOnSave": true,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-annoying
3
- Version: 0.5.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~=1.15.0; extra == 'dev'
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~=6.0.0; extra == 'test'
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
  ![PyPI - Version](https://img.shields.io/pypi/v/aws-annoying)
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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![CI](https://github.com/lasuillard/aws-annoying/actions/workflows/ci.yaml/badge.svg)](https://github.com/lasuillard/aws-annoying/actions/workflows/ci.yaml)
5
+ [![codecov](https://codecov.io/gh/lasuillard/aws-annoying/graph/badge.svg?token=gbcHMVVz2k)](https://codecov.io/gh/lasuillard/aws-annoying)
6
+ ![PyPI - Version](https://img.shields.io/pypi/v/aws-annoying)
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
@@ -0,0 +1,3 @@
1
+ from . import task_definition_lifecycle, wait_for_deployment
2
+
3
+ __all__ = ("task_definition_lifecycle", "wait_for_deployment")
@@ -0,0 +1,9 @@
1
+ import typer
2
+
3
+ from aws_annoying.cli.app import app
4
+
5
+ ecs_app = typer.Typer(
6
+ no_args_is_help=True,
7
+ help="ECS (Elastic Container Service) utility commands.",
8
+ )
9
+ app.add_typer(ecs_app, name="ecs")
@@ -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 .app import app
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
- @app.command()
18
- def ecs_task_definition_lifecycle(
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
- print("⚠️ Dry run mode enabled. Will not perform any actual changes.")
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
- print(f"⚠️ Deregistering {len(expired_taskdef_arns)} task definitions...")
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
- print(f"Deregistered task definition [yellow]{family_revision!r}[/yellow]")
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
- print(f"⚠️ Deleting {len(expired_taskdef_arns)} task definitions in chunks of size {_DELETE_CHUNK_SIZE}...")
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
- print(f"Deleted {len(chunk)} task definitions in {idx}-th batch.")
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.")