aws-annoying 0.5.0__py3-none-any.whl → 0.7.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 (33) 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 +158 -0
  6. aws_annoying/cli/load_variables.py +20 -25
  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 -32
  11. aws_annoying/cli/session_manager/install.py +8 -5
  12. aws_annoying/cli/session_manager/port_forward.py +22 -12
  13. aws_annoying/cli/session_manager/start.py +13 -5
  14. aws_annoying/cli/session_manager/stop.py +9 -7
  15. aws_annoying/ecs/__init__.py +25 -0
  16. aws_annoying/ecs/check.py +39 -0
  17. aws_annoying/ecs/common.py +8 -0
  18. aws_annoying/ecs/errors.py +14 -0
  19. aws_annoying/ecs/wait_for.py +190 -0
  20. aws_annoying/{mfa.py → mfa_config.py} +7 -2
  21. aws_annoying/session_manager/session_manager.py +2 -4
  22. aws_annoying/session_manager/shortcuts.py +10 -6
  23. aws_annoying/utils/downloader.py +1 -8
  24. aws_annoying/utils/ec2.py +33 -0
  25. aws_annoying/utils/platform.py +11 -0
  26. aws_annoying/utils/timeout.py +85 -0
  27. aws_annoying/{variables.py → variable_loader.py} +11 -16
  28. {aws_annoying-0.5.0.dist-info → aws_annoying-0.7.0.dist-info}/METADATA +48 -3
  29. aws_annoying-0.7.0.dist-info/RECORD +42 -0
  30. aws_annoying-0.5.0.dist-info/RECORD +0 -31
  31. {aws_annoying-0.5.0.dist-info → aws_annoying-0.7.0.dist-info}/WHEEL +0 -0
  32. {aws_annoying-0.5.0.dist-info → aws_annoying-0.7.0.dist-info}/entry_points.txt +0 -0
  33. {aws_annoying-0.5.0.dist-info → aws_annoying-0.7.0.dist-info}/licenses/LICENSE +0 -0
aws_annoying/cli/app.py CHANGED
@@ -1,6 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import importlib.metadata
4
+ import logging
5
+ import logging.config
6
+ from typing import Optional
7
+
3
8
  import typer
9
+ from rich import print # noqa: A004
10
+ from rich.console import Console
4
11
 
5
12
  app = typer.Typer(
6
13
  pretty_exceptions_short=True,
@@ -8,3 +15,77 @@ app = typer.Typer(
8
15
  rich_markup_mode="rich",
9
16
  no_args_is_help=True,
10
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.")
@@ -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,19 @@ 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)
86
-
87
81
  command = ctx.args
88
82
  if not command:
89
- console.print("⚠️ No command provided. Exiting...")
83
+ logger.warning("No command provided. Exiting...")
90
84
  raise typer.Exit(0)
91
85
 
92
86
  # Mapping of the ARNs by index (index used for ordering)
93
87
  map_arns_by_index = {str(idx): arn for idx, arn in enumerate(arns)}
94
88
  if env_prefix:
95
- console.print(f"🔍 Loading ARNs from environment variables with prefix: {env_prefix!r}")
89
+ logger.info("Loading ARNs from environment variables with prefix: %r", env_prefix)
96
90
  arns_env = {
97
91
  key.removeprefix(env_prefix): value for key, value in os.environ.items() if key.startswith(env_prefix)
98
92
  }
99
- console.print(f"🔍 Found {len(arns_env)} sources from environment variables.")
93
+ logger.info("Found %d sources from environment variables.", len(arns_env))
100
94
  map_arns_by_index = arns_env | map_arns_by_index
101
95
 
102
96
  # Briefly show the ARNs
@@ -104,21 +98,22 @@ def load_variables( # noqa: PLR0913
104
98
  for idx, arn in sorted(map_arns_by_index.items()):
105
99
  table.add_row(idx, arn)
106
100
 
107
- console.print(table)
101
+ # Workaround: The logger cannot directly handle the rich table output.
102
+ with StringIO() as file:
103
+ Console(file=file, emoji=False).print(table)
104
+ table_str = file.getvalue().rstrip()
105
+ logger.info("Summary:\n%s", table_str)
108
106
 
109
107
  # Retrieve the variables
110
- loader = VariableLoader(dry_run=dry_run)
111
- console.print("🔍 Retrieving variables from AWS resources...")
112
- if dry_run:
113
- console.print("⚠️ Dry run mode enabled. Variables won't be loaded from AWS.")
114
-
108
+ loader = VariableLoader()
109
+ logger.info("Retrieving variables from AWS resources...")
115
110
  try:
116
111
  variables, load_stats = loader.load(map_arns_by_index)
117
112
  except Exception as exc: # noqa: BLE001
118
- console.print(f"Failed to load the variables: {exc!s}")
113
+ logger.error("Failed to load the variables: %s", exc) # noqa: TRY400
119
114
  raise typer.Exit(1) from None
120
115
 
121
- console.print(f"Retrieved {load_stats['secrets']} secrets and {load_stats['parameters']} parameters.")
116
+ logger.info("Retrieved %d secrets and %d parameters.", load_stats["secrets"], load_stats["parameters"])
122
117
 
123
118
  # Prepare the environment variables
124
119
  env = os.environ.copy()
@@ -130,7 +125,7 @@ def load_variables( # noqa: PLR0913
130
125
  env.setdefault(key, str(value))
131
126
 
132
127
  # 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]")
128
+ logger.info("Running the command: [bold orchid]%s[/bold orchid]", " ".join(command))
134
129
  if replace: # pragma: no cover (not coverable)
135
130
  os.execvpe(command[0], command, env=env) # noqa: S606
136
131
  # 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
aws_annoying/cli/main.py CHANGED
@@ -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
@@ -1,18 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  from pathlib import Path # noqa: TC003
4
5
  from typing import Optional
5
6
 
6
7
  import boto3
7
8
  import typer
8
- from rich import print # noqa: A004
9
9
  from rich.prompt import Prompt
10
10
 
11
- from aws_annoying.mfa import MfaConfig, update_credentials
11
+ from aws_annoying.mfa_config import MfaConfig, update_credentials
12
12
 
13
13
  from ._app import mfa_app
14
14
 
15
- _CONFIG_INI_SECTION = "aws-annoying:mfa"
15
+ logger = logging.getLogger(__name__)
16
16
 
17
17
 
18
18
  @mfa_app.command()
@@ -44,6 +44,10 @@ def configure( # noqa: PLR0913
44
44
  "~/.aws/config",
45
45
  help="The path to the AWS config file. Used to persist the MFA configuration.",
46
46
  ),
47
+ aws_config_section: str = typer.Option(
48
+ "aws-annoying:mfa",
49
+ help="The section in the AWS config file to persist the MFA configuration.",
50
+ ),
47
51
  persist: bool = typer.Option(
48
52
  True, # noqa: FBT003
49
53
  help="Persist the MFA configuration.",
@@ -55,9 +59,9 @@ def configure( # noqa: PLR0913
55
59
  aws_config = aws_config.expanduser()
56
60
 
57
61
  # Load configuration
58
- mfa_config, exists = MfaConfig.from_ini_file(aws_config, _CONFIG_INI_SECTION)
62
+ mfa_config, exists = MfaConfig.from_ini_file(aws_config, aws_config_section)
59
63
  if exists:
60
- print(f"⚙️ Loaded MFA configuration from AWS config ({aws_config}).")
64
+ logger.info("Loaded MFA configuration from AWS config (%s).", aws_config)
61
65
 
62
66
  mfa_profile = (
63
67
  mfa_profile
@@ -83,7 +87,7 @@ def configure( # noqa: PLR0913
83
87
  )
84
88
 
85
89
  # Get credentials
86
- print(f"💬 Retrieving MFA credentials using profile [bold]{mfa_source_profile}[/bold]")
90
+ logger.info("Retrieving MFA credentials using profile [bold]%s[/bold]", mfa_source_profile)
87
91
  session = boto3.session.Session(profile_name=mfa_source_profile)
88
92
  sts = session.client("sts")
89
93
  response = sts.get_session_token(
@@ -93,7 +97,11 @@ def configure( # noqa: PLR0913
93
97
  credentials = response["Credentials"]
94
98
 
95
99
  # Update MFA profile in AWS credentials
96
- print(f"✅ Updating MFA profile ([bold]{mfa_profile}[/bold]) to AWS credentials ({aws_credentials})")
100
+ logger.warning(
101
+ "Updating MFA profile ([bold]%s[/bold]) to AWS credentials ([bold]%s[/bold])",
102
+ mfa_profile,
103
+ aws_credentials,
104
+ )
97
105
  update_credentials(
98
106
  aws_credentials,
99
107
  mfa_profile, # type: ignore[arg-type]
@@ -104,13 +112,14 @@ def configure( # noqa: PLR0913
104
112
 
105
113
  # Persist MFA configuration
106
114
  if persist:
107
- print(
108
- f"Persisting MFA configuration in AWS config ({aws_config}),"
109
- f" in [bold]{_CONFIG_INI_SECTION}[/bold] section.",
115
+ logger.info(
116
+ "Persisting MFA configuration in AWS config (%s), in [bold]%s[/bold] section.",
117
+ aws_config,
118
+ aws_config_section,
110
119
  )
111
120
  mfa_config.mfa_profile = mfa_profile
112
121
  mfa_config.mfa_source_profile = mfa_source_profile
113
122
  mfa_config.mfa_serial_number = mfa_serial_number
114
- mfa_config.save_ini_file(aws_config, _CONFIG_INI_SECTION)
123
+ mfa_config.save_ini_file(aws_config, aws_config_section)
115
124
  else:
116
- print("⚠️ MFA configuration not persisted.")
125
+ logger.warning("MFA configuration not persisted.")
@@ -1,10 +1,7 @@
1
- # TODO(lasuillard): Using this file until split CLI from library codebase
2
1
  from __future__ import annotations
3
2
 
4
- import re
5
3
  from typing import Any
6
4
 
7
- import boto3
8
5
  import typer
9
6
  from rich.prompt import Confirm
10
7
 
@@ -17,38 +14,10 @@ class SessionManager(_SessionManager):
17
14
  if self._confirm:
18
15
  return
19
16
 
20
- confirm = Confirm.ask(f"⚠️ Will run the following command: [bold red]{' '.join(command)}[/bold red]. Proceed?")
17
+ confirm = Confirm.ask(f"Will run the following command: [bold red]{' '.join(command)}[/bold red]. Proceed?")
21
18
  if not confirm:
22
19
  raise typer.Abort
23
20
 
24
21
  def install(self, *args: Any, confirm: bool = False, **kwargs: Any) -> None:
25
22
  self._confirm = confirm
26
23
  return super().install(*args, **kwargs)
27
-
28
-
29
- def get_instance_id_by_name(name_or_id: str) -> str | None:
30
- """Get the EC2 instance ID by name or ID.
31
-
32
- Be aware that this function will only return the first instance found
33
- with the given name, no matter how many instances are found.
34
-
35
- Args:
36
- name_or_id: The name or ID of the EC2 instance.
37
-
38
- Returns:
39
- The instance ID if found, otherwise `None`.
40
- """
41
- if re.match(r"m?i-.+", name_or_id):
42
- return name_or_id
43
-
44
- ec2 = boto3.client("ec2")
45
- response = ec2.describe_instances(Filters=[{"Name": "tag:Name", "Values": [name_or_id]}])
46
- reservations = response["Reservations"]
47
- if not reservations:
48
- return None
49
-
50
- instances = reservations[0]["Instances"]
51
- if not instances:
52
- return None
53
-
54
- return str(instances[0]["InstanceId"])