aws-annoying 0.4.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

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 (32) hide show
  1. aws_annoying/cli/app.py +81 -0
  2. aws_annoying/cli/ecs/__init__.py +3 -0
  3. aws_annoying/cli/ecs/_app.py +9 -0
  4. aws_annoying/cli/{ecs_task_definition_lifecycle.py → ecs/task_definition_lifecycle.py} +18 -13
  5. aws_annoying/cli/ecs/wait_for_deployment.py +94 -0
  6. aws_annoying/cli/load_variables.py +22 -22
  7. aws_annoying/cli/logging_handler.py +52 -0
  8. aws_annoying/cli/main.py +1 -1
  9. aws_annoying/cli/mfa/configure.py +21 -12
  10. aws_annoying/cli/session_manager/_common.py +1 -2
  11. aws_annoying/cli/session_manager/install.py +10 -7
  12. aws_annoying/cli/session_manager/port_forward.py +41 -38
  13. aws_annoying/cli/session_manager/start.py +48 -2
  14. aws_annoying/cli/session_manager/stop.py +9 -7
  15. aws_annoying/ecs/__init__.py +17 -0
  16. aws_annoying/ecs/common.py +8 -0
  17. aws_annoying/ecs/deployment_waiter.py +274 -0
  18. aws_annoying/ecs/errors.py +14 -0
  19. aws_annoying/{mfa.py → mfa_config.py} +7 -2
  20. aws_annoying/session_manager/__init__.py +8 -1
  21. aws_annoying/session_manager/session_manager.py +26 -39
  22. aws_annoying/session_manager/shortcuts.py +76 -0
  23. aws_annoying/utils/ec2.py +36 -0
  24. aws_annoying/utils/platform.py +11 -0
  25. aws_annoying/utils/timeout.py +88 -0
  26. aws_annoying/{variables.py → variable_loader.py} +11 -16
  27. {aws_annoying-0.4.0.dist-info → aws_annoying-0.6.0.dist-info}/METADATA +47 -2
  28. aws_annoying-0.6.0.dist-info/RECORD +41 -0
  29. aws_annoying-0.4.0.dist-info/RECORD +0 -30
  30. {aws_annoying-0.4.0.dist-info → aws_annoying-0.6.0.dist-info}/WHEEL +0 -0
  31. {aws_annoying-0.4.0.dist-info → aws_annoying-0.6.0.dist-info}/entry_points.txt +0 -0
  32. {aws_annoying-0.4.0.dist-info → aws_annoying-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple
13
13
 
14
14
  import boto3
15
15
 
16
- from aws_annoying.utils.platform import command_as_root, is_root, os_release
16
+ from aws_annoying.utils.platform import command_as_root, is_root, is_windows, os_release
17
17
 
18
18
  from .errors import PluginNotInstalledError, UnsupportedPlatformError
19
19
 
@@ -22,21 +22,17 @@ if TYPE_CHECKING:
22
22
 
23
23
  logger = logging.getLogger(__name__)
24
24
 
25
- # TODO(lasuillard): Platform checking is spread everywhere, should be moved to a single place
26
-
27
25
 
28
26
  class SessionManager:
29
27
  """AWS Session Manager plugin manager."""
30
28
 
31
- def __init__(self, *, session: boto3.session.Session | None = None, downloader: AbstractDownloader) -> None:
29
+ def __init__(self, *, session: boto3.session.Session | None = None) -> None:
32
30
  """Initialize SessionManager.
33
31
 
34
32
  Args:
35
33
  session: Boto3 session to use for AWS operations.
36
- downloader: File downloader to use for downloading the plugin.
37
34
  """
38
35
  self.session = session or boto3.session.Session()
39
- self.downloader = downloader
40
36
 
41
37
  # ------------------------------------------------------------------------
42
38
  # Installation
@@ -67,7 +63,7 @@ class SessionManager:
67
63
  """Get the path to the session-manager-plugin binary."""
68
64
  binary_path_str = shutil.which("session-manager-plugin")
69
65
  if not binary_path_str:
70
- if platform.system() == "Windows":
66
+ if is_windows():
71
67
  # Windows: use the default installation path
72
68
  binary_path = (
73
69
  Path(os.environ["ProgramFiles"]) # noqa: SIM112
@@ -90,6 +86,7 @@ class SessionManager:
90
86
  linux_distribution: _LinuxDistribution | None = None,
91
87
  arch: str | None = None,
92
88
  root: bool | None = None,
89
+ downloader: AbstractDownloader,
93
90
  ) -> None:
94
91
  """Install AWS Session Manager plugin.
95
92
 
@@ -99,17 +96,23 @@ class SessionManager:
99
96
  If `None` and current `os` is `"Linux"`, will try to detect the distribution from current system.
100
97
  arch: The architecture to install the plugin on. If `None`, will use the current architecture.
101
98
  root: Whether to run the installation as root. If `None`, will check if the current user is root.
99
+ downloader: File downloader to use for downloading the plugin.
102
100
  """
103
101
  os = os or platform.system()
104
102
  arch = arch or platform.machine()
105
103
 
106
104
  if os == "Windows":
107
- self._install_windows()
105
+ self._install_windows(downloader=downloader)
108
106
  elif os == "Darwin":
109
- self._install_macos(arch=arch, root=root or is_root())
107
+ self._install_macos(arch=arch, root=root or is_root(), downloader=downloader)
110
108
  elif os == "Linux":
111
109
  linux_distribution = linux_distribution or _detect_linux_distribution()
112
- self._install_linux(linux_distribution=linux_distribution, arch=arch, root=root or is_root())
110
+ self._install_linux(
111
+ linux_distribution=linux_distribution,
112
+ arch=arch,
113
+ root=root or is_root(),
114
+ downloader=downloader,
115
+ )
113
116
  else:
114
117
  msg = f"Unsupported operating system: {os}"
115
118
  raise UnsupportedPlatformError(msg)
@@ -118,20 +121,20 @@ class SessionManager:
118
121
  """Hook to run before invoking plugin installation command."""
119
122
 
120
123
  # https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-windows.html
121
- def _install_windows(self) -> None:
124
+ def _install_windows(self, *, downloader: AbstractDownloader) -> None:
122
125
  """Install session-manager-plugin on Windows via EXE installer."""
123
126
  download_url = (
124
127
  "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/windows/SessionManagerPluginSetup.exe"
125
128
  )
126
129
  with tempfile.TemporaryDirectory() as temp_dir:
127
130
  p = Path(temp_dir)
128
- exe_installer = self.downloader.download(download_url, to=p / "SessionManagerPluginSetup.exe")
131
+ exe_installer = downloader.download(download_url, to=p / "SessionManagerPluginSetup.exe")
129
132
  command = [str(exe_installer), "/quiet"]
130
133
  self.before_install(command)
131
134
  subprocess.call(command, cwd=p) # noqa: S603
132
135
 
133
136
  # https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-macos-overview.html
134
- def _install_macos(self, *, arch: str, root: bool) -> None:
137
+ def _install_macos(self, *, arch: str, root: bool, downloader: AbstractDownloader) -> None:
135
138
  """Install session-manager-plugin on macOS via signed installer."""
136
139
  # ! Intel chip will not be supported
137
140
  if arch == "x86_64":
@@ -148,7 +151,7 @@ class SessionManager:
148
151
 
149
152
  with tempfile.TemporaryDirectory() as temp_dir:
150
153
  p = Path(temp_dir)
151
- pkg_installer = self.downloader.download(download_url, to=p / "session-manager-plugin.pkg")
154
+ pkg_installer = downloader.download(download_url, to=p / "session-manager-plugin.pkg")
152
155
 
153
156
  # Run installer
154
157
  command = command_as_root(
@@ -179,6 +182,7 @@ class SessionManager:
179
182
  linux_distribution: _LinuxDistribution,
180
183
  arch: str,
181
184
  root: bool,
185
+ downloader: AbstractDownloader,
182
186
  ) -> None:
183
187
  name = linux_distribution.name
184
188
  version = linux_distribution.version
@@ -200,7 +204,7 @@ class SessionManager:
200
204
 
201
205
  with tempfile.TemporaryDirectory() as temp_dir:
202
206
  p = Path(temp_dir)
203
- deb_installer = self.downloader.download(download_url, to=p / "session-manager-plugin.deb")
207
+ deb_installer = downloader.download(download_url, to=p / "session-manager-plugin.deb")
204
208
 
205
209
  # Invoke installation command
206
210
  command = command_as_root(["dpkg", "--install", str(deb_installer)], root=root)
@@ -241,28 +245,25 @@ class SessionManager:
241
245
  raise UnsupportedPlatformError(msg)
242
246
 
243
247
  # ------------------------------------------------------------------------
244
- # Session
248
+ # Command
245
249
  # ------------------------------------------------------------------------
246
- def start(
250
+ def build_command(
247
251
  self,
248
- *,
249
252
  target: str,
250
253
  document_name: str,
251
254
  parameters: dict[str, Any],
252
255
  reason: str | None = None,
253
- log_file: Path | None = None,
254
- ) -> subprocess.Popen:
255
- """Start new session.
256
+ ) -> list[str]:
257
+ """Build command for starting a session.
256
258
 
257
259
  Args:
258
- target: The target instance ID or name.
260
+ target: The target instance ID.
259
261
  document_name: The SSM document name to use for the session.
260
262
  parameters: The parameters to pass to the SSM document.
261
263
  reason: The reason for starting the session.
262
- log_file: Optional file to log output to.
263
264
 
264
265
  Returns:
265
- Process ID of the session.
266
+ The command to start the session.
266
267
  """
267
268
  is_installed, binary_path, version = self.verify_installation()
268
269
  if not is_installed:
@@ -279,7 +280,7 @@ class SessionManager:
279
280
  )
280
281
 
281
282
  region = self.session.region_name
282
- command = [
283
+ return [
283
284
  str(binary_path),
284
285
  json.dumps(response),
285
286
  region,
@@ -289,20 +290,6 @@ class SessionManager:
289
290
  f"https://ssm.{region}.amazonaws.com",
290
291
  ]
291
292
 
292
- stdout: subprocess._FILE
293
- if log_file is not None: # noqa: SIM108
294
- stdout = log_file.open(mode="at+", buffering=1)
295
- else:
296
- stdout = subprocess.DEVNULL
297
-
298
- return subprocess.Popen( # noqa: S603
299
- command,
300
- stdout=stdout,
301
- stderr=subprocess.STDOUT,
302
- text=True,
303
- close_fds=False, # FD inherited from parent process
304
- )
305
-
306
293
 
307
294
  # ? Could be moved to utils, but didn't because it's too specific to this module
308
295
  class _LinuxDistribution(NamedTuple):
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import subprocess
5
+ from contextlib import contextmanager
6
+ from typing import TYPE_CHECKING
7
+
8
+ from aws_annoying.utils.timeout import Timeout
9
+
10
+ from .session_manager import SessionManager
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterator
14
+
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @contextmanager
20
+ def port_forward( # noqa: PLR0913
21
+ *,
22
+ through: str,
23
+ local_port: int,
24
+ remote_host: str,
25
+ remote_port: int,
26
+ reason: str | None = None,
27
+ start_timeout: int | None = None,
28
+ ) -> Iterator[subprocess.Popen[str]]:
29
+ """Context manager for port forwarding sessions.
30
+
31
+ Args:
32
+ through: The instance ID to use as port-forwarding proxy.
33
+ local_port: The local port to listen to.
34
+ remote_host: The remote host to connect to.
35
+ remote_port: The remote port to connect to.
36
+ reason: The reason for starting the session.
37
+ start_timeout: The timeout in seconds to wait for the session to start.
38
+
39
+ Returns:
40
+ The command to start the session.
41
+ """
42
+ session_manager = SessionManager()
43
+ command = session_manager.build_command(
44
+ target=through,
45
+ document_name="AWS-StartPortForwardingSessionToRemoteHost",
46
+ parameters={
47
+ "localPortNumber": [str(local_port)],
48
+ "host": [remote_host],
49
+ "portNumber": [str(remote_port)],
50
+ },
51
+ reason=reason,
52
+ )
53
+ try:
54
+ proc = subprocess.Popen( # noqa: S603
55
+ command,
56
+ stdout=subprocess.PIPE,
57
+ stderr=subprocess.STDOUT,
58
+ text=True,
59
+ )
60
+
61
+ # * Must be unreachable
62
+ if proc.stdout is None:
63
+ msg = "Standard output is not available"
64
+ raise RuntimeError(msg)
65
+
66
+ # Wait for the session to start
67
+ # ? Not sure this is trustworthy health check
68
+ with Timeout(start_timeout):
69
+ for line in proc.stdout:
70
+ if "Waiting for connections..." in line:
71
+ logger.info("Session started successfully.")
72
+ break
73
+
74
+ yield proc
75
+ finally:
76
+ proc.terminate()
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ import boto3
6
+
7
+
8
+ def get_instance_id_by_name(name_or_id: str, *, session: boto3.session.Session | None = None) -> str | None:
9
+ """Get the EC2 instance ID by name or ID.
10
+
11
+ Be aware that this function will only return the first instance found
12
+ with the given name, no matter how many instances are found.
13
+
14
+ Args:
15
+ name_or_id: The name or ID of the EC2 instance.
16
+ session: The boto3 session to use. If not provided, a new session will be created.
17
+
18
+ Returns:
19
+ The instance ID if found, otherwise `None`.
20
+ """
21
+ if re.match(r"^m?i-[0-9a-f]+$", name_or_id):
22
+ return name_or_id
23
+
24
+ session = session or boto3.session.Session()
25
+ ec2 = session.client("ec2")
26
+
27
+ response = ec2.describe_instances(Filters=[{"Name": "tag:Name", "Values": [name_or_id]}])
28
+ reservations = response["Reservations"]
29
+ if not reservations:
30
+ return None
31
+
32
+ instances = reservations[0]["Instances"]
33
+ if not instances:
34
+ return None
35
+
36
+ return str(instances[0]["InstanceId"])
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
+ import platform
4
5
  from pathlib import Path
5
6
 
6
7
 
@@ -25,3 +26,13 @@ def os_release() -> dict[str, str]:
25
26
  key.strip('"'): value.strip('"')
26
27
  for key, value in (line.split("=", 1) for line in content.splitlines() if "=" in line)
27
28
  }
29
+
30
+
31
+ def is_macos() -> bool:
32
+ """Check if the current OS is macOS."""
33
+ return platform.system() == "Darwin"
34
+
35
+
36
+ def is_windows() -> bool:
37
+ """Check if the current OS is Windows."""
38
+ return platform.system() == "Windows"
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import signal
4
+ from functools import wraps
5
+ from typing import TYPE_CHECKING, Callable, Optional, TypeVar, cast
6
+
7
+ from pydantic import PositiveInt, validate_call
8
+
9
+ from aws_annoying.utils.platform import is_windows
10
+
11
+ if TYPE_CHECKING:
12
+ from types import FrameType
13
+ from typing import Any
14
+
15
+
16
+ class OperationTimeoutError(Exception):
17
+ """Operation timed out."""
18
+
19
+
20
+ _F = TypeVar("_F", bound=Callable)
21
+
22
+
23
+ class Timeout:
24
+ """Timeout handler utilizing signals.
25
+
26
+ This utility relies on Unix signals (`signal.SIGALRM`). The behavior will be dummied
27
+ to do nothing on Windows OS.
28
+ """
29
+
30
+ @validate_call
31
+ def __init__(self, seconds: Optional[PositiveInt]) -> None:
32
+ """Initialize timeout handler.
33
+
34
+ Args:
35
+ seconds: The timeout in seconds. `None` means no timeout,
36
+ allowing the function to run normally.
37
+
38
+ """
39
+ self.timeout_seconds = seconds
40
+
41
+ self._signal_handler_registered = False
42
+
43
+ def _set_signal_handler(self) -> None:
44
+ if is_windows() or self.timeout_seconds is None:
45
+ return
46
+
47
+ signal.signal(signal.SIGALRM, self._handler)
48
+ signal.alarm(self.timeout_seconds)
49
+ self._signal_handler_registered = True
50
+
51
+ def _handler(self, signum: int, frame: FrameType | None) -> Any: # noqa: ARG002
52
+ msg = "Timeout reached"
53
+ raise OperationTimeoutError(msg)
54
+
55
+ def _reset_signal_handler(self) -> None:
56
+ if is_windows() or not self._signal_handler_registered:
57
+ return
58
+
59
+ signal.signal(signal.SIGALRM, signal.SIG_IGN)
60
+ signal.alarm(0)
61
+ self._signal_handler_registered = False
62
+
63
+ def __call__(self, func: _F) -> _F:
64
+ """Decorator to set a timeout for a function.
65
+
66
+ Please note, using this decorator in nested functions may not work properly as
67
+ the signal handler for outer functions may not be resumed correctly.
68
+
69
+ Raises:
70
+ OperationTimeoutError: When timeout is reached.
71
+ """
72
+
73
+ @wraps(func)
74
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
75
+ self._set_signal_handler()
76
+ try:
77
+ return func(*args, **kwargs)
78
+ finally:
79
+ self._reset_signal_handler()
80
+
81
+ return cast("_F", wrapper)
82
+
83
+ def __enter__(self) -> None:
84
+ self._set_signal_handler()
85
+
86
+ def __exit__(self, *args: object) -> Any:
87
+ self._reset_signal_handler()
88
+ return False # Re-raise
@@ -2,17 +2,17 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import json
5
+ import logging
5
6
  from typing import Any, TypedDict
6
7
 
7
8
  import boto3
8
9
 
10
+ logger = logging.getLogger(__name__)
11
+
9
12
  # Type aliases for readability
10
13
  _ARN = str
11
14
  _Variables = dict[str, Any]
12
15
 
13
- # TODO(lasuillard): Need some refactoring (with #2, #3)
14
- # TODO(lasuillard): Put some logging
15
-
16
16
 
17
17
  class _LoadStatsDict(TypedDict):
18
18
  secrets: int
@@ -20,14 +20,13 @@ class _LoadStatsDict(TypedDict):
20
20
 
21
21
 
22
22
  class VariableLoader: # noqa: D101
23
- def __init__(self, *, dry_run: bool) -> None:
24
- """Initialize the VariableLoader.
23
+ def __init__(self, *, session: boto3.session.Session | None = None) -> None:
24
+ """Initialize variable loader.
25
25
 
26
26
  Args:
27
- dry_run: Whether to run in dry-run mode.
28
- console: Rich console instance.
27
+ session: Boto3 session to use for AWS operations.
29
28
  """
30
- self.dry_run = dry_run
29
+ self.session = session or boto3.session.Session()
31
30
 
32
31
  # TODO(lasuillard): Currently not using pagination (do we need more than 10-20 secrets or parameters each?)
33
32
  # ; consider adding it if needed
@@ -54,12 +53,8 @@ class VariableLoader: # noqa: D101
54
53
  # Retrieve variables from AWS resources
55
54
  secrets: dict[str, _Variables]
56
55
  parameters: dict[str, _Variables]
57
- if self.dry_run:
58
- secrets = {idx: {} for idx, _ in secrets_map.items()}
59
- parameters = {idx: {} for idx, _ in parameters_map.items()}
60
- else:
61
- secrets = self._retrieve_secrets(secrets_map)
62
- parameters = self._retrieve_parameters(parameters_map)
56
+ secrets = self._retrieve_secrets(secrets_map)
57
+ parameters = self._retrieve_parameters(parameters_map)
63
58
 
64
59
  load_stats: _LoadStatsDict = {
65
60
  "secrets": len(secrets),
@@ -79,7 +74,7 @@ class VariableLoader: # noqa: D101
79
74
  if not secrets_map:
80
75
  return {}
81
76
 
82
- secretsmanager = boto3.client("secretsmanager")
77
+ secretsmanager = self.session.client("secretsmanager")
83
78
 
84
79
  # Retrieve the secrets
85
80
  arns = list(secrets_map.values())
@@ -108,7 +103,7 @@ class VariableLoader: # noqa: D101
108
103
  if not parameters_map:
109
104
  return {}
110
105
 
111
- ssm = boto3.client("ssm")
106
+ ssm = self.session.client("ssm")
112
107
 
113
108
  # Retrieve the parameters
114
109
  parameter_names = list(parameters_map.values())
@@ -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,41 @@
1
+ aws_annoying/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ aws_annoying/mfa_config.py,sha256=z0GpRhLHEWaaXbECV4Ei4oNM1WCFoEZAxCIPbpY4Ymc,2200
3
+ aws_annoying/variable_loader.py,sha256=N9qPPHG6mzSIIHrWJnJ_FV5kKZxssOaTHoAQEwiDE3s,4569
4
+ aws_annoying/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ aws_annoying/cli/app.py,sha256=6opsAfIFGPnUSK68s7sEnFzLhipnevfP2cDJ0yj7Lh0,2366
6
+ aws_annoying/cli/load_variables.py,sha256=83RGt5eA_TJtza3kyLXoSM_A3lfQs6TNC2TCrWYia_I,5032
7
+ aws_annoying/cli/logging_handler.py,sha256=JPsZePV3YH9e1nLv0Q3fqSeXSEfZ5cGJo6eLK2F4oxE,1426
8
+ aws_annoying/cli/main.py,sha256=bU4Gxic5_3qrrd8l9eN709-D4o_OHgrdH91FS9Xs8zI,477
9
+ aws_annoying/cli/ecs/__init__.py,sha256=IxfaMXcGU6WTHE_RXj-aitXtSg25j5m3HGTG9O02GjI,125
10
+ aws_annoying/cli/ecs/_app.py,sha256=izD0VL55i7oG-2CtWCV21bSoAeE-DZbxyJ5pi6VXhjU,200
11
+ aws_annoying/cli/ecs/task_definition_lifecycle.py,sha256=W7zr8zEPjjHgss9Tnb3QMcJ_45Yb9SJ5D1B315vb9kc,2803
12
+ aws_annoying/cli/ecs/wait_for_deployment.py,sha256=0lzl5t0TOvJJeMxIA6if2TbG7bJFf6JulhPHZqNXjFM,3169
13
+ aws_annoying/cli/mfa/__init__.py,sha256=rbEGhw5lOQZV_XAc3nSbo56JVhsSPpeOgEtiAy9qzEA,50
14
+ aws_annoying/cli/mfa/_app.py,sha256=Ub7gxb6kGF3Ve1ucQSOjHmc4jAu8mxgegcXsIbOzLLQ,189
15
+ aws_annoying/cli/mfa/configure.py,sha256=s2SCz7gzvIE9mJM7w95JPwoGhuQrgkFhjUd6qjD169k,3957
16
+ aws_annoying/cli/session_manager/__init__.py,sha256=FkT6jT6OXduOURN61d-U6hgd-XluQbvuVtKXXiXgSEk,105
17
+ aws_annoying/cli/session_manager/_app.py,sha256=OVOHW0iyKzunvaqLhjoseHw1-WxJ1gGb7QmiyAEezyY,221
18
+ aws_annoying/cli/session_manager/_common.py,sha256=Uj-MF7z8Qntd24Z03xxE-jSKcgrsd8xl41E6db4qCtY,711
19
+ aws_annoying/cli/session_manager/install.py,sha256=VIk6313jUf6THwvs06AOUQwhTjzCGCcvDXMxyz0BcgE,1477
20
+ aws_annoying/cli/session_manager/port_forward.py,sha256=upsUsd7MkUfaAP3o0ZK6N-SkOBnr1ogwqYozG4TDTzY,4331
21
+ aws_annoying/cli/session_manager/start.py,sha256=1Q-WFvbQkMgVntwIrM2HfpUKmVL38aFrjJjG3aA0fzU,1447
22
+ aws_annoying/cli/session_manager/stop.py,sha256=QjjOmmhZr_0IInhOyHvSlodk7Nx4qu9EtrRw4kl8Hks,1535
23
+ aws_annoying/ecs/__init__.py,sha256=Bohwe4-jxF1cxQmErCXl0l5ma08HpWmV2PqdAS-gf4w,432
24
+ aws_annoying/ecs/common.py,sha256=TvP27SEvdIBnA92Oude-oDCy1SuaYNdtpokkbpZmdzo,139
25
+ aws_annoying/ecs/deployment_waiter.py,sha256=e3xWShvr9piraoEMJdZun2GuffkyvTI76-riECfoe9A,9936
26
+ aws_annoying/ecs/errors.py,sha256=n9j_h1MDUV6IVabKgwbCVAiPZQNJDJ5rVRHA82Je5QQ,429
27
+ aws_annoying/session_manager/__init__.py,sha256=IENviL3ux2LF7o9xFGYEiqaGw03hxnyNX2btbB1xyEU,318
28
+ aws_annoying/session_manager/errors.py,sha256=YioKlRtZ-GUP0F_ts_ebw7-HYkxe8mTes6HK821Kuiw,353
29
+ aws_annoying/session_manager/session_manager.py,sha256=pUsyJ_w9UzdIfHA2Z8kU6UcZxOqypFXH1rl6pDytdqo,12046
30
+ aws_annoying/session_manager/shortcuts.py,sha256=Yn4wCl43lfttrZ7GbzfGua2jZHe5Fe6ClEy4ikg-Q_s,2143
31
+ aws_annoying/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ aws_annoying/utils/debugger.py,sha256=UFllDCGI2gPtwo1XS5vqw0qyR6bYr7XknmBwSxalKIc,754
33
+ aws_annoying/utils/downloader.py,sha256=aB5RzT-LpbFX24-2HXlAkdgVowc4TR9FWT_K8WwZ1BE,1923
34
+ aws_annoying/utils/ec2.py,sha256=RjEA5eO53c-H6VE6cZBeB1RJo1rxpDQFhbZTYdANbs8,1062
35
+ aws_annoying/utils/platform.py,sha256=TBIzCzYiFj36HmndZedegvFlxPSNtBQyAxzuwelvxNg,985
36
+ aws_annoying/utils/timeout.py,sha256=PjfFtiLALh7lQvchtMYOfjTNrfuZwCaeaPKW16EpM5c,2483
37
+ aws_annoying-0.6.0.dist-info/METADATA,sha256=68sKE-Atya0ibgT1ljJsaKHpnKXHDq-x7EdUy0yRIqI,3503
38
+ aws_annoying-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
+ aws_annoying-0.6.0.dist-info/entry_points.txt,sha256=DcKE5V0WvVJ8wUOHxyUz1yLAJOuuJUgRPlMcQ4O7jEs,66
40
+ aws_annoying-0.6.0.dist-info/licenses/LICENSE,sha256=Q5GkvYijQ2KTQ-QWhv43ilzCno4ZrzrEuATEQZd9rYo,1067
41
+ aws_annoying-0.6.0.dist-info/RECORD,,
@@ -1,30 +0,0 @@
1
- aws_annoying/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- aws_annoying/mfa.py,sha256=m6-V1bWeUWsAmRddl-lv13mPCMnftoPzJoNnZ0kiaWQ,2007
3
- aws_annoying/variables.py,sha256=a9cMS9JU-XA2h1tztO7ofixoDEpqtS_eVEiWrQ75mTo,4761
4
- aws_annoying/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- aws_annoying/cli/app.py,sha256=sp50uVoAl4D6Wk3DFpzKZzSsxmSxNYejFxm62b_Kxps,201
6
- aws_annoying/cli/ecs_task_definition_lifecycle.py,sha256=O36Bf5LBnVJNyYmdlUxhtsIHNoxky1t5YacAXiL9UEI,2803
7
- aws_annoying/cli/load_variables.py,sha256=eWNByUEc1ijF8uCe_egdAnjWxfMNCZeVr0vtTtQLe3Y,5086
8
- aws_annoying/cli/main.py,sha256=TSzPeMkgIgKFf3bom_vDkFYK0bHF1r5K9ADreZUV3k4,503
9
- aws_annoying/cli/mfa/__init__.py,sha256=rbEGhw5lOQZV_XAc3nSbo56JVhsSPpeOgEtiAy9qzEA,50
10
- aws_annoying/cli/mfa/_app.py,sha256=Ub7gxb6kGF3Ve1ucQSOjHmc4jAu8mxgegcXsIbOzLLQ,189
11
- aws_annoying/cli/mfa/configure.py,sha256=vsoHfTVFF2dPgiYsp2L-EkMwtAA0_-tVwFd6Wv6DscU,3746
12
- aws_annoying/cli/session_manager/__init__.py,sha256=FkT6jT6OXduOURN61d-U6hgd-XluQbvuVtKXXiXgSEk,105
13
- aws_annoying/cli/session_manager/_app.py,sha256=OVOHW0iyKzunvaqLhjoseHw1-WxJ1gGb7QmiyAEezyY,221
14
- aws_annoying/cli/session_manager/_common.py,sha256=0fCm6Zx6P1NcyyiHlSoB9PgIdrxzUXLVPqKJWQnJ8I4,792
15
- aws_annoying/cli/session_manager/install.py,sha256=zX2cu-IBYlf9yl8z9P0CTQz72BP6aAMo-KnolC_PGsU,1443
16
- aws_annoying/cli/session_manager/port_forward.py,sha256=uMcsafTNAHZh-0E1yWXCliMivWyuWV2K9DdVhBZ9pG8,4327
17
- aws_annoying/cli/session_manager/start.py,sha256=1yaMuy-7IWrrDoPHjgygOP_-g_tajfnpaVPZanxsZxs,215
18
- aws_annoying/cli/session_manager/stop.py,sha256=ttU6nlbVgBkZDtY-DwUyCstv5TFtat5TljkyuY8QICU,1482
19
- aws_annoying/session_manager/__init__.py,sha256=swbRdFh9CdO6tzyBjSxN0KC7MMASvptIpbsijUPIZgI,243
20
- aws_annoying/session_manager/errors.py,sha256=YioKlRtZ-GUP0F_ts_ebw7-HYkxe8mTes6HK821Kuiw,353
21
- aws_annoying/session_manager/session_manager.py,sha256=7tqfFPA4bWF0Me_VbUDcq5Cpl1sw3bevGyT8QWfLTOk,12454
22
- aws_annoying/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- aws_annoying/utils/debugger.py,sha256=UFllDCGI2gPtwo1XS5vqw0qyR6bYr7XknmBwSxalKIc,754
24
- aws_annoying/utils/downloader.py,sha256=aB5RzT-LpbFX24-2HXlAkdgVowc4TR9FWT_K8WwZ1BE,1923
25
- aws_annoying/utils/platform.py,sha256=h3DUWmTMM-_4TfTWNqY0uNqyVsBjAuMm2DEbG-daxe8,742
26
- aws_annoying-0.4.0.dist-info/METADATA,sha256=9u5wofuLZXXPrAj1YcOhbqPlPH46jtub8AUvs2KNZ9s,1916
27
- aws_annoying-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
- aws_annoying-0.4.0.dist-info/entry_points.txt,sha256=DcKE5V0WvVJ8wUOHxyUz1yLAJOuuJUgRPlMcQ4O7jEs,66
29
- aws_annoying-0.4.0.dist-info/licenses/LICENSE,sha256=Q5GkvYijQ2KTQ-QWhv43ilzCno4ZrzrEuATEQZd9rYo,1067
30
- aws_annoying-0.4.0.dist-info/RECORD,,