aws-annoying 0.4.0__tar.gz → 0.6.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 (130) hide show
  1. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.devcontainer/onCreateCommand.sh +0 -2
  2. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.devcontainer/postAttachCommand.sh +2 -0
  3. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.github/workflows/ci.yaml +4 -4
  4. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.github/workflows/release.yaml +1 -1
  5. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.pre-commit-config.yaml +2 -0
  6. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.vscode/launch.json +9 -1
  7. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/PKG-INFO +47 -2
  8. aws_annoying-0.6.0/README.md +51 -0
  9. aws_annoying-0.6.0/aws_annoying/cli/app.py +91 -0
  10. aws_annoying-0.6.0/aws_annoying/cli/ecs/__init__.py +3 -0
  11. aws_annoying-0.6.0/aws_annoying/cli/ecs/_app.py +9 -0
  12. aws_annoying-0.4.0/aws_annoying/cli/ecs_task_definition_lifecycle.py → aws_annoying-0.6.0/aws_annoying/cli/ecs/task_definition_lifecycle.py +18 -13
  13. aws_annoying-0.6.0/aws_annoying/cli/ecs/wait_for_deployment.py +94 -0
  14. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/load_variables.py +22 -22
  15. aws_annoying-0.6.0/aws_annoying/cli/logging_handler.py +52 -0
  16. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/main.py +1 -1
  17. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/mfa/configure.py +21 -12
  18. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/session_manager/_common.py +1 -2
  19. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/session_manager/install.py +10 -7
  20. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/session_manager/port_forward.py +41 -38
  21. aws_annoying-0.6.0/aws_annoying/cli/session_manager/start.py +55 -0
  22. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/session_manager/stop.py +9 -7
  23. aws_annoying-0.6.0/aws_annoying/ecs/__init__.py +17 -0
  24. aws_annoying-0.6.0/aws_annoying/ecs/common.py +8 -0
  25. aws_annoying-0.6.0/aws_annoying/ecs/deployment_waiter.py +274 -0
  26. aws_annoying-0.6.0/aws_annoying/ecs/errors.py +14 -0
  27. aws_annoying-0.4.0/aws_annoying/mfa.py → aws_annoying-0.6.0/aws_annoying/mfa_config.py +7 -2
  28. aws_annoying-0.6.0/aws_annoying/session_manager/__init__.py +11 -0
  29. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/session_manager/session_manager.py +26 -39
  30. aws_annoying-0.6.0/aws_annoying/session_manager/shortcuts.py +76 -0
  31. aws_annoying-0.6.0/aws_annoying/utils/ec2.py +36 -0
  32. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/utils/platform.py +11 -0
  33. aws_annoying-0.6.0/aws_annoying/utils/timeout.py +88 -0
  34. aws_annoying-0.4.0/aws_annoying/variables.py → aws_annoying-0.6.0/aws_annoying/variable_loader.py +11 -16
  35. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/console/ecs-exec/ecs-exec.user.js +3 -3
  36. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/pyproject.toml +7 -7
  37. aws_annoying-0.6.0/tests/_helpers.py +9 -0
  38. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/_helpers/string_.py +3 -0
  39. aws_annoying-0.6.0/tests/cli/ecs/snapshots/test_task_definition_lifecycle/test_basic/stdout.txt +16 -0
  40. aws_annoying-0.6.0/tests/cli/ecs/snapshots/test_task_definition_lifecycle/test_dry_run/stdout.txt +17 -0
  41. aws_annoying-0.4.0/tests/cli/test_ecs_task_definition_lifecycle.py → aws_annoying-0.6.0/tests/cli/ecs/test_task_definition_lifecycle.py +6 -5
  42. aws_annoying-0.6.0/tests/cli/mfa/snapshots/test_configure/test_basic/persist/stdout.txt +3 -0
  43. aws_annoying-0.6.0/tests/cli/mfa/snapshots/test_configure/test_basic/skip_persist/stdout.txt +3 -0
  44. aws_annoying-0.6.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt +4 -0
  45. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/mfa/test_configure.py +2 -3
  46. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/snapshots/test_load_variables/test_basic/stdout.txt +4 -3
  47. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/snapshots/test_load_variables/test_env_prefix/stdout.txt +6 -5
  48. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/snapshots/test_load_variables/test_overwrite_env/stdout.txt +6 -5
  49. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/snapshots/test_load_variables/test_resource_not_found/ssm/stdout.txt +3 -2
  50. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/snapshots/test_load_variables/test_unsupported_resource/stdout.txt +3 -2
  51. aws_annoying-0.6.0/tests/cli/test_app.py +34 -0
  52. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/test_load_variables.py +2 -25
  53. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/conftest.py +2 -0
  54. aws_annoying-0.6.0/tests/session_manager/test_errors.py +0 -0
  55. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/session_manager/test_session_manager.py +9 -8
  56. aws_annoying-0.6.0/tests/session_manager/test_shortcuts.py +0 -0
  57. aws_annoying-0.6.0/tests/test_mfa_config.py +0 -0
  58. aws_annoying-0.6.0/tests/test_variable_loader.py +0 -0
  59. aws_annoying-0.6.0/tests/utils/__init__.py +0 -0
  60. aws_annoying-0.6.0/tests/utils/test_downloader.py +0 -0
  61. aws_annoying-0.6.0/tests/utils/test_ec2.py +65 -0
  62. aws_annoying-0.6.0/tests/utils/test_platform.py +0 -0
  63. aws_annoying-0.6.0/tests/utils/test_timeout.py +43 -0
  64. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/uv.lock +1025 -1003
  65. aws_annoying-0.4.0/README.md +0 -8
  66. aws_annoying-0.4.0/aws_annoying/cli/app.py +0 -10
  67. aws_annoying-0.4.0/aws_annoying/cli/session_manager/start.py +0 -9
  68. aws_annoying-0.4.0/aws_annoying/session_manager/__init__.py +0 -4
  69. aws_annoying-0.4.0/tests/cli/mfa/snapshots/test_configure/test_basic/persist/stdout.txt +0 -3
  70. aws_annoying-0.4.0/tests/cli/mfa/snapshots/test_configure/test_basic/skip_persist/stdout.txt +0 -3
  71. aws_annoying-0.4.0/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/stdout.txt +0 -4
  72. aws_annoying-0.4.0/tests/cli/snapshots/test_ecs_task_definition_lifecycle/test_basic/stdout.txt +0 -16
  73. aws_annoying-0.4.0/tests/cli/snapshots/test_ecs_task_definition_lifecycle/test_dry_run/stdout.txt +0 -17
  74. aws_annoying-0.4.0/tests/cli/snapshots/test_load_variables/test_dry_run/stdout.txt +0 -17
  75. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.devcontainer/.env.example +0 -0
  76. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.devcontainer/Dockerfile +0 -0
  77. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.devcontainer/devcontainer.json +0 -0
  78. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.devcontainer/docker-compose.devcontainer.yaml +0 -0
  79. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.editorconfig +0 -0
  80. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.gitattributes +0 -0
  81. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.github/dependabot.yaml +0 -0
  82. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.gitignore +0 -0
  83. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.python-version +0 -0
  84. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.vscode/extensions.json +0 -0
  85. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/.vscode/settings.json +0 -0
  86. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/LICENSE +0 -0
  87. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/Makefile +0 -0
  88. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/__init__.py +0 -0
  89. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/__init__.py +0 -0
  90. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/mfa/__init__.py +0 -0
  91. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/mfa/_app.py +0 -0
  92. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/session_manager/__init__.py +0 -0
  93. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/cli/session_manager/_app.py +0 -0
  94. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/session_manager/errors.py +0 -0
  95. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/utils/__init__.py +0 -0
  96. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/utils/debugger.py +0 -0
  97. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/aws_annoying/utils/downloader.py +0 -0
  98. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/console/ecs-exec/README.md +0 -0
  99. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/console/ecs-exec/ecs-console.png +0 -0
  100. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/console/ecs-exec/session-manager.png +0 -0
  101. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/examples/dbeaver/README.md +0 -0
  102. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/examples/dbeaver/after-disconnect.png +0 -0
  103. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/examples/dbeaver/before-connect.png +0 -0
  104. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/examples/dbeaver/new-connection.png +0 -0
  105. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/examples/dbeaver/test-connection.png +0 -0
  106. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/__init__.py +0 -0
  107. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/__init__.py +0 -0
  108. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/_helpers/__init__.py +0 -0
  109. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/_helpers/command_builder.py +0 -0
  110. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/_helpers/invoke.py +0 -0
  111. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/_helpers/scripts/printenv.py +0 -0
  112. {aws_annoying-0.4.0/tests/cli/mfa → aws_annoying-0.6.0/tests/cli/ecs}/__init__.py +0 -0
  113. /aws_annoying-0.4.0/tests/cli/session_manager/__init__.py → /aws_annoying-0.6.0/tests/cli/ecs/test_wait_for_deployment.py +0 -0
  114. {aws_annoying-0.4.0/tests/session_manager → aws_annoying-0.6.0/tests/cli/mfa}/__init__.py +0 -0
  115. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/mfa/snapshots/test_configure/test_basic/persist/aws_config.ini +0 -0
  116. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/mfa/snapshots/test_configure/test_load_existing_config/aws_config.ini +0 -0
  117. {aws_annoying-0.4.0/tests/utils → aws_annoying-0.6.0/tests/cli/session_manager}/__init__.py +0 -0
  118. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/session_manager/test_install.py +0 -0
  119. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/session_manager/test_port_forward.py +0 -0
  120. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/session_manager/test_start.py +0 -0
  121. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/session_manager/test_stop.py +0 -0
  122. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/snapshots/test_load_variables/test_nothing/stdout.txt +0 -0
  123. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/snapshots/test_load_variables/test_replace_quiet/stdout.txt +0 -0
  124. /aws_annoying-0.4.0/tests/cli/test_app.py → /aws_annoying-0.6.0/tests/cli/test_logging_handler.py +0 -0
  125. {aws_annoying-0.4.0 → aws_annoying-0.6.0}/tests/cli/test_main.py +0 -0
  126. /aws_annoying-0.4.0/tests/session_manager/test_errors.py → /aws_annoying-0.6.0/tests/ecs/__init__.py +0 -0
  127. /aws_annoying-0.4.0/tests/test_mfa.py → /aws_annoying-0.6.0/tests/ecs/test_common.py +0 -0
  128. /aws_annoying-0.4.0/tests/test_variables.py → /aws_annoying-0.6.0/tests/ecs/test_deployment_waiter.py +0 -0
  129. /aws_annoying-0.4.0/tests/utils/test_downloader.py → /aws_annoying-0.6.0/tests/ecs/test_errors.py +0 -0
  130. /aws_annoying-0.4.0/tests/utils/test_platform.py → /aws_annoying-0.6.0/tests/session_manager/__init__.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
@@ -73,7 +73,7 @@ jobs:
73
73
  if: ${{ matrix.os == 'macos-latest' }}
74
74
  run: |
75
75
  brew uninstall session-manager-plugin
76
- uv run pytest -m macos
76
+ uv run pytest -m 'not docker'
77
77
 
78
78
  - name: Run tests (Windows)
79
79
  if: ${{ matrix.os == 'windows-latest' }}
@@ -83,7 +83,7 @@ jobs:
83
83
  -OutFile "uninstall.bat"
84
84
 
85
85
  Start-Process "cmd.exe" -ArgumentList "/c uninstall.bat" -Wait
86
- uv run pytest -m windows
86
+ uv run pytest -m 'not docker'
87
87
 
88
88
  - name: Upload test results to Codecov
89
89
  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
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-annoying
3
- Version: 0.4.0
3
+ Version: 0.6.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
@@ -22,13 +22,15 @@ 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,94 @@
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 DeploymentFailedError, ECSDeploymentWaiter, ECSServiceRef
10
+ from aws_annoying.utils.timeout import OperationTimeoutError, Timeout
11
+
12
+ from ._app import ecs_app
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @ecs_app.command()
18
+ def wait_for_deployment( # noqa: PLR0913
19
+ *,
20
+ cluster: str = typer.Option(
21
+ ...,
22
+ help="The name of the ECS cluster.",
23
+ show_default=False,
24
+ ),
25
+ service: str = typer.Option(
26
+ ...,
27
+ help="The name of the ECS service.",
28
+ show_default=False,
29
+ ),
30
+ expected_task_definition: Optional[str] = typer.Option(
31
+ None,
32
+ help=(
33
+ "The service's task definition expected after deployment."
34
+ " If provided, it will be used to assert the service's task definition after deployment finished or timed out." # noqa: E501
35
+ ),
36
+ show_default=False,
37
+ ),
38
+ polling_interval: int = typer.Option(
39
+ 5,
40
+ help="The interval between any polling attempts, in seconds.",
41
+ min=1,
42
+ ),
43
+ timeout_seconds: Optional[int] = typer.Option(
44
+ None,
45
+ help=(
46
+ "The maximum time to wait for the deployment to complete, in seconds."
47
+ " If not provided, it will wait indefinitely."
48
+ ),
49
+ min=1,
50
+ ),
51
+ wait_for_start: bool = typer.Option(
52
+ True, # noqa: FBT003
53
+ help=(
54
+ "Whether to wait for the deployment to start."
55
+ " Because there could be no deployment right after the deploy,"
56
+ " this option will wait for a new deployment to start if no running deployment is found."
57
+ ),
58
+ ),
59
+ wait_for_stability: bool = typer.Option(
60
+ False, # noqa: FBT003
61
+ help="Whether to wait for the service to be stable after the deployment.",
62
+ ),
63
+ ) -> None:
64
+ """Wait for ECS deployment to complete."""
65
+ start = datetime.now(tz=timezone.utc)
66
+ waiter = ECSDeploymentWaiter(ECSServiceRef(cluster=cluster, service=service))
67
+ try:
68
+ with Timeout(timeout_seconds):
69
+ waiter.wait(
70
+ wait_for_start=wait_for_start,
71
+ polling_interval=polling_interval,
72
+ wait_for_stability=wait_for_stability,
73
+ expected_task_definition=expected_task_definition,
74
+ )
75
+ except OperationTimeoutError:
76
+ logger.error( # noqa: TRY400
77
+ "Timeout reached after %s seconds. The deployment may not have finished.",
78
+ timeout_seconds,
79
+ )
80
+ raise typer.Exit(1) from None
81
+ except DeploymentFailedError as err:
82
+ elapsed = datetime.now(tz=timezone.utc) - start
83
+ logger.error( # noqa: TRY400
84
+ "Deployment failed in [bold]%.2f[/bold] seconds with error: %s",
85
+ elapsed.total_seconds(),
86
+ err,
87
+ )
88
+ raise typer.Exit(1) from None
89
+ else:
90
+ elapsed = datetime.now(tz=timezone.utc) - start
91
+ logger.info(
92
+ "Deployment completed in [bold]%.2f[/bold] seconds.",
93
+ elapsed.total_seconds(),
94
+ )
@@ -1,18 +1,22 @@
1
1
  # flake8: noqa: B008
2
2
  from __future__ import annotations
3
3
 
4
+ import logging
4
5
  import os
5
6
  import subprocess
7
+ from io import StringIO
6
8
  from typing import NoReturn, Optional
7
9
 
8
10
  import typer
9
11
  from rich.console import Console
10
12
  from rich.table import Table
11
13
 
12
- from aws_annoying.variables import VariableLoader
14
+ from aws_annoying.variable_loader import VariableLoader
13
15
 
14
16
  from .app import app
15
17
 
18
+ logger = logging.getLogger(__name__)
19
+
16
20
 
17
21
  @app.command(
18
22
  context_settings={
@@ -21,9 +25,9 @@ from .app import app
21
25
  "ignore_unknown_options": True,
22
26
  },
23
27
  )
24
- def load_variables( # noqa: PLR0913
25
- *,
28
+ def load_variables(
26
29
  ctx: typer.Context,
30
+ *,
27
31
  arns: list[str] = typer.Option(
28
32
  [],
29
33
  metavar="ARN",
@@ -42,14 +46,6 @@ def load_variables( # noqa: PLR0913
42
46
  False, # noqa: FBT003
43
47
  help="Overwrite the existing environment variables with the same name.",
44
48
  ),
45
- quiet: bool = typer.Option(
46
- False, # noqa: FBT003
47
- help="Suppress all outputs from this command.",
48
- ),
49
- dry_run: bool = typer.Option(
50
- False, # noqa: FBT003
51
- help="Print the progress only. Neither load variables nor run the command.",
52
- ),
53
49
  replace: bool = typer.Option(
54
50
  True, # noqa: FBT003
55
51
  help=(
@@ -82,21 +78,21 @@ def load_variables( # noqa: PLR0913
82
78
  The variables are loaded in the order of option provided, overwriting the variables with the same name in the order of the ARNs.
83
79
  Existing environment variables are preserved by default, unless `--overwrite-env` is provided.
84
80
  """ # noqa: E501
85
- console = Console(quiet=quiet, emoji=False)
81
+ dry_run = ctx.meta["dry_run"]
86
82
 
87
83
  command = ctx.args
88
84
  if not command:
89
- console.print("⚠️ No command provided. Exiting...")
85
+ logger.warning("No command provided. Exiting...")
90
86
  raise typer.Exit(0)
91
87
 
92
88
  # Mapping of the ARNs by index (index used for ordering)
93
89
  map_arns_by_index = {str(idx): arn for idx, arn in enumerate(arns)}
94
90
  if env_prefix:
95
- console.print(f"🔍 Loading ARNs from environment variables with prefix: {env_prefix!r}")
91
+ logger.info("Loading ARNs from environment variables with prefix: %r", env_prefix)
96
92
  arns_env = {
97
93
  key.removeprefix(env_prefix): value for key, value in os.environ.items() if key.startswith(env_prefix)
98
94
  }
99
- console.print(f"🔍 Found {len(arns_env)} sources from environment variables.")
95
+ logger.info("Found %d sources from environment variables.", len(arns_env))
100
96
  map_arns_by_index = arns_env | map_arns_by_index
101
97
 
102
98
  # Briefly show the ARNs
@@ -104,21 +100,25 @@ def load_variables( # noqa: PLR0913
104
100
  for idx, arn in sorted(map_arns_by_index.items()):
105
101
  table.add_row(idx, arn)
106
102
 
107
- console.print(table)
103
+ # Workaround: The logger cannot directly handle the rich table output.
104
+ with StringIO() as file:
105
+ Console(file=file, emoji=False).print(table)
106
+ table_str = file.getvalue().rstrip()
107
+ logger.info("Summary:\n%s", table_str)
108
108
 
109
109
  # Retrieve the variables
110
- loader = VariableLoader(dry_run=dry_run)
111
- console.print("🔍 Retrieving variables from AWS resources...")
110
+ loader = VariableLoader()
111
+ logger.info("Retrieving variables from AWS resources...")
112
112
  if dry_run:
113
- console.print("⚠️ Dry run mode enabled. Variables won't be loaded from AWS.")
113
+ logger.warning("Dry run mode enabled. Variables won't be loaded from AWS.")
114
114
 
115
115
  try:
116
116
  variables, load_stats = loader.load(map_arns_by_index)
117
117
  except Exception as exc: # noqa: BLE001
118
- console.print(f"Failed to load the variables: {exc!s}")
118
+ logger.error("Failed to load the variables: %s", exc) # noqa: TRY400
119
119
  raise typer.Exit(1) from None
120
120
 
121
- console.print(f"Retrieved {load_stats['secrets']} secrets and {load_stats['parameters']} parameters.")
121
+ logger.info("Retrieved %d secrets and %d parameters.", load_stats["secrets"], load_stats["parameters"])
122
122
 
123
123
  # Prepare the environment variables
124
124
  env = os.environ.copy()
@@ -130,7 +130,7 @@ def load_variables( # noqa: PLR0913
130
130
  env.setdefault(key, str(value))
131
131
 
132
132
  # Run the command with the variables injected as environment variables, replacing current process
133
- console.print(f"🚀 Running the command: [bold orchid]{' '.join(command)}[/bold orchid]")
133
+ logger.info("Running the command: [bold orchid]%s[/bold orchid]", " ".join(command))
134
134
  if replace: # pragma: no cover (not coverable)
135
135
  os.execvpe(command[0], command, env=env) # noqa: S606
136
136
  # The above line should never return
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import logging.config
5
+ from typing import TYPE_CHECKING, Any, Final
6
+
7
+ from typing_extensions import override
8
+
9
+ if TYPE_CHECKING:
10
+ from rich.console import Console
11
+
12
+
13
+ class RichLogHandler(logging.Handler):
14
+ """Custom logging handler to use Rich Console."""
15
+
16
+ _level_emojis: Final[dict[str, str]] = {
17
+ "DEBUG": "🔍",
18
+ "INFO": "ℹ️", # noqa: RUF001
19
+ "WARNING": "⚠️",
20
+ "ERROR": "❗",
21
+ "CRITICAL": "🚨",
22
+ }
23
+
24
+ def __init__(self, console: Console, *args: Any, **kwargs: Any) -> None:
25
+ """Initialize the log handler.
26
+
27
+ Args:
28
+ console: Rich console instance.
29
+ *args: Additional arguments for the logging handler.
30
+ **kwargs: Additional keyword arguments for the logging handler.
31
+ """
32
+ super().__init__(*args, **kwargs)
33
+ self.console = console
34
+
35
+ @override
36
+ def emit(self, record: logging.LogRecord) -> None:
37
+ msg = self.format(record)
38
+ self.console.print(msg)
39
+
40
+ @override
41
+ def format(self, record: logging.LogRecord) -> str:
42
+ """Format the log record.
43
+
44
+ Args:
45
+ record: The log record to format.
46
+
47
+ Returns:
48
+ The formatted log message.
49
+ """
50
+ msg = super().format(record)
51
+ emoji = self._level_emojis.get(record.levelname)
52
+ return f"{emoji} {msg}" if emoji else msg
@@ -1,7 +1,7 @@
1
1
  # flake8: noqa: F401
2
2
  from __future__ import annotations
3
3
 
4
- import aws_annoying.cli.ecs_task_definition_lifecycle
4
+ import aws_annoying.cli.ecs
5
5
  import aws_annoying.cli.load_variables
6
6
  import aws_annoying.cli.mfa
7
7
  import aws_annoying.cli.session_manager