leverage 2.2.0rc1__tar.gz → 2.2.0rc3__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 (31) hide show
  1. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/PKG-INFO +2 -3
  2. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/__init__.py +1 -1
  3. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/_utils.py +7 -67
  4. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/leverage.py +6 -13
  5. leverage-2.2.0rc3/leverage/modules/__init__.py +7 -0
  6. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/auth.py +0 -1
  7. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/aws.py +18 -3
  8. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/credentials.py +82 -65
  9. leverage-2.2.0rc3/leverage/modules/kubectl.py +188 -0
  10. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/project.py +14 -12
  11. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/tf.py +3 -3
  12. leverage-2.2.0rc3/leverage/modules/tfautomv.py +43 -0
  13. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/tfrunner.py +19 -5
  14. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/utils.py +10 -4
  15. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/path.py +10 -2
  16. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/pyproject.toml +2 -4
  17. leverage-2.2.0rc1/leverage/container.py +0 -716
  18. leverage-2.2.0rc1/leverage/containers/kubectl.py +0 -145
  19. leverage-2.2.0rc1/leverage/modules/__init__.py +0 -9
  20. leverage-2.2.0rc1/leverage/modules/kubectl.py +0 -43
  21. leverage-2.2.0rc1/leverage/modules/tfautomv.py +0 -39
  22. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/LICENSE.txt +0 -0
  23. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/README.md +0 -0
  24. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/_backend_config.py +0 -0
  25. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/_internals.py +0 -0
  26. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/_parsing.py +0 -0
  27. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/conf.py +0 -0
  28. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/logger.py +0 -0
  29. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/run.py +0 -0
  30. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/runner.py +0 -0
  31. {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/tasks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: leverage
3
- Version: 2.2.0rc1
3
+ Version: 2.2.0rc3
4
4
  Summary: Binbash Leverage Command-Line tool.
5
5
  Home-page: https://github.com/binbashar/leverage
6
6
  License: MIT
@@ -21,10 +21,9 @@ Classifier: Topic :: Software Development :: Build Tools
21
21
  Requires-Dist: boto3 (==1.33.2)
22
22
  Requires-Dist: click (==8.0.1)
23
23
  Requires-Dist: configupdater (==3.2)
24
- Requires-Dist: docker (==6.1.0)
25
- Requires-Dist: dockerpty (==0.4.1)
26
24
  Requires-Dist: docutils (==0.17.1)
27
25
  Requires-Dist: jinja2 (==3.0.1)
26
+ Requires-Dist: prompt-toolkit (==3.0.51)
28
27
  Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
29
28
  Requires-Dist: python-hcl2 (==7.3.1)
30
29
  Requires-Dist: questionary (==1.10.0)
@@ -4,7 +4,7 @@
4
4
 
5
5
  # pylint: disable=wrong-import-position
6
6
 
7
- __version__ = "2.2.0rc1"
7
+ __version__ = "2.2.0rc3"
8
8
 
9
9
  import sys
10
10
  from shutil import which
@@ -8,10 +8,8 @@ from typing import List, Optional
8
8
 
9
9
  import hcl2
10
10
  import lark
11
- from click.exceptions import Exit
11
+ from click.exceptions import ClickException
12
12
  from configupdater import ConfigUpdater
13
- from docker import DockerClient
14
- from docker.models.containers import Container
15
13
 
16
14
  from leverage import logger
17
15
 
@@ -64,56 +62,17 @@ def git(command):
64
62
  run(command, stdout=PIPE, stderr=PIPE, check=True)
65
63
 
66
64
 
67
- class CustomEntryPoint:
68
- """
69
- Set a custom entrypoint on the container while entering the context.
70
- Once outside, return it to its original value.
71
- """
72
-
73
- def __init__(self, container, entrypoint):
74
- self.container = container
75
- self.old_entrypoint = container.entrypoint
76
- self.new_entrypoint = entrypoint
77
-
78
- def __enter__(self):
79
- self.container.entrypoint = self.new_entrypoint
80
-
81
- def __exit__(self, *args, **kwargs):
82
- self.container.entrypoint = self.old_entrypoint
83
-
84
-
85
- class AwsCredsEntryPoint(CustomEntryPoint):
86
- """
87
- Fetching AWS credentials by setting the SSO/MFA entrypoints.
88
- """
89
-
90
- def __init__(self, container, override_entrypoint=None):
91
- auth_method = container.auth_method()
92
-
93
- new_entrypoint = f"{auth_method}{container.entrypoint if override_entrypoint is None else override_entrypoint}"
94
- super(AwsCredsEntryPoint, self).__init__(container, entrypoint=new_entrypoint)
95
-
96
- def __exit__(self, *args, **kwargs):
97
- super(AwsCredsEntryPoint, self).__exit__(*args, **kwargs)
98
- if self.container.mfa_enabled:
99
- self.container.environment.update(
100
- {
101
- "AWS_SHARED_CREDENTIALS_FILE": self.container.environment["AWS_SHARED_CREDENTIALS_FILE"].replace(
102
- ".aws", "tmp"
103
- ),
104
- "AWS_CONFIG_FILE": self.container.environment["AWS_CONFIG_FILE"].replace(".aws", "tmp"),
105
- }
106
- )
107
-
108
-
109
- class ExitError(Exit):
65
+ class ExitError(ClickException):
110
66
  """
111
67
  Raise an Exit exception but also print an error description.
112
68
  """
113
69
 
114
70
  def __init__(self, exit_code: int, error_description: str):
115
- logger.error(error_description)
116
- super(ExitError, self).__init__(exit_code)
71
+ self.exit_code = exit_code
72
+ super(ExitError, self).__init__(message=error_description)
73
+
74
+ def show(self):
75
+ logger.error(self.message)
117
76
 
118
77
 
119
78
  def parse_tf_file(file: Path):
@@ -134,25 +93,6 @@ def parse_tf_file(file: Path):
134
93
  return parsed
135
94
 
136
95
 
137
- class ContainerSession:
138
- """
139
- Handle the start/stop cycle of a container.
140
- Useful when you need to keep your container alive to share context between multiple commands.
141
- """
142
-
143
- def __init__(self, docker_client: DockerClient, container_data):
144
- self.docker_client = docker_client
145
- self.container_data = container_data
146
-
147
- def __enter__(self) -> Container:
148
- self.docker_client.api.start(self.container_data)
149
- return self.docker_client.containers.get(self.container_data["Id"])
150
-
151
- def __exit__(self, exc_type, exc_value, exc_tb):
152
- self.docker_client.api.stop(self.container_data)
153
- self.docker_client.api.remove_container(self.container_data)
154
-
155
-
156
96
  def key_finder(d: dict, target: str, avoid: Optional[str] = None) -> List[str]:
157
97
  """
158
98
  Iterate over a dict of dicts and/or lists of dicts, looking for a key with value "target".
@@ -2,19 +2,12 @@
2
2
  Binbash Leverage Command-line tool.
3
3
  """
4
4
 
5
- import rich
6
- from packaging.version import Version
7
-
8
5
  import click
9
6
 
10
7
  from leverage import __version__, conf
11
8
  from leverage._internals import pass_state
12
-
13
- # from leverage.modules.credentials import credentials
14
- from leverage.modules import aws, run, tofu, terraform
15
-
16
- # from leverage.modules import run, project, tofu, terraform, tfautomv, kubectl, shell
17
9
  from leverage.path import NotARepositoryError, PathsHandler
10
+ from leverage.modules import aws, credentials, run, project, tofu, terraform, tfautomv, kubectl
18
11
 
19
12
 
20
13
  @click.group(invoke_without_command=True)
@@ -39,12 +32,12 @@ def leverage(context, state, verbose):
39
32
 
40
33
  # Add modules to leverage
41
34
  leverage.add_command(run)
42
- # leverage.add_command(project)
35
+ leverage.add_command(project)
43
36
  leverage.add_command(tofu)
44
37
  leverage.add_command(tofu, name="tf")
45
38
  leverage.add_command(terraform)
46
- # leverage.add_command(credentials)
39
+ leverage.add_command(credentials)
47
40
  leverage.add_command(aws)
48
- # leverage.add_command(tfautomv)
49
- # leverage.add_command(kubectl)
50
- # leverage.add_command(kubectl, name="kc")
41
+ leverage.add_command(tfautomv)
42
+ leverage.add_command(kubectl)
43
+ leverage.add_command(kubectl, name="kc")
@@ -0,0 +1,7 @@
1
+ from .run import run
2
+ from .project import project
3
+ from .aws import aws
4
+ from .tf import tofu, terraform
5
+ from .credentials import credentials
6
+ from .tfautomv import tfautomv
7
+ from .kubectl import kubectl
@@ -2,7 +2,6 @@ import time
2
2
  import json
3
3
  from pathlib import Path
4
4
  from datetime import datetime
5
- from dateutil.tz import tzutc
6
5
  from configparser import NoSectionError, NoOptionError
7
6
 
8
7
  import boto3
@@ -13,9 +13,9 @@ from leverage import logger
13
13
  from leverage.path import PathsHandler
14
14
  from leverage.modules.runner import Runner
15
15
  from leverage.modules.utils import _handle_subcommand
16
- from leverage.modules.auth import get_sso_access_token
17
16
  from leverage._utils import get_or_create_section, ExitError
18
17
  from leverage._internals import pass_state, pass_runner, pass_paths
18
+ from leverage.modules.auth import get_sso_access_token, check_sso_token, refresh_layer_credentials
19
19
 
20
20
 
21
21
  CONTEXT_SETTINGS = {"ignore_unknown_options": True}
@@ -24,6 +24,21 @@ CONTEXT_SETTINGS = {"ignore_unknown_options": True}
24
24
  AWS_SSO_LOGIN_URL = "{sso_url}/#/device?user_code={user_code}"
25
25
 
26
26
 
27
+ @pass_paths
28
+ def refresh_aws_credentials(paths: PathsHandler) -> None:
29
+ """
30
+ Refresh the AWS credentials for the current project.
31
+ """
32
+ check_sso_token(paths)
33
+
34
+ try: # if we are not in a layer, we don't need to refresh the credentials
35
+ paths.check_for_layer_location()
36
+ except ExitError:
37
+ return
38
+
39
+ refresh_layer_credentials(paths)
40
+
41
+
27
42
  def get_account_roles(sso_client: Any, access_token: str) -> Dict[str, Dict[str, str]]:
28
43
  """
29
44
  Fetch the accounts and roles from the user.
@@ -100,7 +115,7 @@ def configure_sso_profiles(paths: PathsHandler) -> None:
100
115
  @pass_state
101
116
  @click.pass_context
102
117
  def aws(context: click.Context, state: Any, args: Tuple[str, ...]) -> None:
103
- """Run AWS CLI commands in a custom containerized environment."""
118
+ """Run AWS CLI commands in the context of the current project."""
104
119
 
105
120
  credentials_env_vars = {
106
121
  "AWS_SHARED_CREDENTIALS_FILE": str(state.paths.aws_credentials_file),
@@ -115,7 +130,7 @@ def aws(context: click.Context, state: Any, args: Tuple[str, ...]) -> None:
115
130
  env_vars=credentials_env_vars,
116
131
  )
117
132
 
118
- _handle_subcommand(context=context, runner=state.runner, args=args)
133
+ _handle_subcommand(context=context, runner=state.runner, args=args, pre_invocation_callback=refresh_aws_credentials)
119
134
 
120
135
 
121
136
  @aws.group(invoke_without_command=True, add_help_option=False, context_settings=CONTEXT_SETTINGS)
@@ -5,23 +5,26 @@
5
5
  import csv
6
6
  import json
7
7
  import re
8
- from functools import wraps
9
8
  from pathlib import Path
9
+ from functools import wraps
10
+ from typing import Optional, Union
10
11
 
11
12
  import click
12
13
  import questionary
13
- from click.exceptions import Exit
14
- from questionary import Choice
15
14
  from ruamel.yaml import YAML
15
+ from questionary import Choice
16
+ from click.exceptions import Exit
16
17
 
17
18
  from leverage import logger
18
- from leverage._internals import pass_state
19
19
  from leverage._utils import ExitError
20
- from leverage.container import AWSCLIContainer
21
- from leverage.container import get_docker_client
22
- from leverage.path import NotARepositoryError
23
- from leverage.path import get_global_config_path
24
- from leverage.path import get_project_root_or_current_dir_path
20
+ from leverage.modules.runner import Runner
21
+ from leverage._internals import State, pass_runner, pass_paths, pass_state
22
+ from leverage.path import (
23
+ NotARepositoryError,
24
+ PathsHandler,
25
+ get_global_config_path,
26
+ get_project_root_or_current_dir_path,
27
+ )
25
28
 
26
29
  # Regexes for general validation
27
30
  PROJECT_SHORT = r"[a-z]{2,4}"
@@ -212,9 +215,6 @@ def _ask_for_credentials():
212
215
  return list(credentials.values())
213
216
 
214
217
 
215
- AWSCLI = None
216
-
217
-
218
218
  def _load_project_yaml():
219
219
  """Load project.yaml file contents."""
220
220
  if not PROJECT_CONFIG.exists():
@@ -252,7 +252,7 @@ def credentials(state):
252
252
  raise an exception
253
253
 
254
254
  If we reached the only common.tfvars scenario, we have no project name nor TF_IMAGE_TAG.
255
- So the best chance is to read the common.tfvars directly without a conatiner, e.g. with sed or grep
255
+ So the best chance is to read the common.tfvars directly without a container, e.g. with sed or grep
256
256
  """
257
257
  project_config = _load_project_yaml()
258
258
  build_env = Path(f"{PROJECT_ROOT}/build.env")
@@ -287,13 +287,23 @@ def credentials(state):
287
287
  else:
288
288
  logger.info("Reading info from build.env")
289
289
 
290
- state.container = AWSCLIContainer(get_docker_client())
291
- state.container.ensure_image()
292
- global AWSCLI
293
- AWSCLI = state.container
290
+ credentials_env_vars = {
291
+ "AWS_SHARED_CREDENTIALS_FILE": str(state.paths.aws_credentials_file),
292
+ "AWS_CONFIG_FILE": str(state.paths.aws_config_file),
293
+ }
294
+ state.runner = Runner(
295
+ binary="aws",
296
+ error_message=(
297
+ f"AWS CLI not found on system. "
298
+ f"Please install it following the instructions at: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
299
+ ),
300
+ env_vars=credentials_env_vars,
301
+ )
294
302
 
295
303
 
296
- def _load_configs_for_credentials():
304
+ @pass_state
305
+ @pass_paths
306
+ def _load_configs_for_credentials(paths: PathsHandler, state: State):
297
307
  """Load all required values to configure credentials.
298
308
 
299
309
  Raises:
@@ -306,11 +316,11 @@ def _load_configs_for_credentials():
306
316
  project_config = _load_project_yaml()
307
317
 
308
318
  logger.info("Loading project environment configuration file.")
309
- env_config = AWSCLI.env_conf
319
+ env_config = state.config
310
320
 
311
321
  terraform_config = {}
312
322
  logger.info("Loading tf common configuration.")
313
- terraform_config = AWSCLI.paths.common_conf
323
+ terraform_config = paths.common_conf
314
324
 
315
325
  config_values = {}
316
326
  config_values["short_name"] = (
@@ -321,7 +331,7 @@ def _load_configs_for_credentials():
321
331
  )
322
332
  config_values["project_name"] = project_config.get("project_name") or terraform_config.get("project_long")
323
333
 
324
- # region_primary was added in refarch v1
334
+ # region_primary was added in ref-arch v1
325
335
  # for v2 it was replaced by region at project level
326
336
  region_primary = "region_primary"
327
337
  if not "region_primary" in project_config and not "region_primary" in terraform_config:
@@ -355,7 +365,8 @@ def _load_configs_for_credentials():
355
365
  return config_values
356
366
 
357
367
 
358
- def _profile_is_configured(profile):
368
+ @pass_runner
369
+ def _profile_is_configured(awscli: Runner, profile: str):
359
370
  """Check if given profile is already configured.
360
371
 
361
372
  Args:
@@ -364,7 +375,7 @@ def _profile_is_configured(profile):
364
375
  Returns:
365
376
  bool: Whether the profile was already configured or not.
366
377
  """
367
- exit_code, _ = AWSCLI.exec("configure list", profile)
378
+ exit_code, _ = awscli.exec("configure", "list", "--profile", profile)
368
379
 
369
380
  return not exit_code
370
381
 
@@ -384,7 +395,7 @@ def _extract_credentials(file):
384
395
  Returns:
385
396
  str, str: Key ID, Secret Key
386
397
  """
387
- with open(file) as access_keys_file:
398
+ with open(file, encoding="utf-8-sig") as access_keys_file:
388
399
  try:
389
400
  keys = next(csv.DictReader(access_keys_file))
390
401
 
@@ -407,19 +418,15 @@ def _extract_credentials(file):
407
418
  return access_key_id, secret_access_key
408
419
 
409
420
 
410
- def _backup_file(filename):
411
- """Create backup of a credential file using docker image.
412
-
413
- Args:
414
- filename (str): File to backup, either `config` or `credentials`
415
- """
416
- credential_files_env_vars = {"config": "AWS_CONFIG_FILE", "credentials": "AWS_SHARED_CREDENTIALS_FILE"}
417
- env_var = credential_files_env_vars.get(filename)
418
-
419
- AWSCLI.system_exec(f"sh -c 'cp ${env_var} \"${{{env_var}}}.bkp\"'")
420
-
421
-
422
- def configure_credentials(profile, file=None, make_backup=False):
421
+ @pass_paths
422
+ @pass_runner
423
+ def configure_credentials(
424
+ awscli: Runner,
425
+ paths: PathsHandler,
426
+ profile: str,
427
+ file: Optional[Union[Path, str]] = None,
428
+ make_backup: bool = False,
429
+ ):
423
430
  """Set credentials in `credentials` file for AWS cli. Make backup if required.
424
431
 
425
432
  Args:
@@ -437,17 +444,18 @@ def configure_credentials(profile, file=None, make_backup=False):
437
444
 
438
445
  if make_backup:
439
446
  logger.info("Backing up credentials file.")
440
- _backup_file("credentials")
447
+ Path(paths.aws_credentials_file).copy(Path(paths.aws_credentials_file).with_suffix(".bkp"))
441
448
 
442
449
  values = {"aws_access_key_id": key_id, "aws_secret_access_key": secret_key}
443
450
 
444
451
  for key, value in values.items():
445
- exit_code, output = AWSCLI.exec(f"configure set {key} {value}", profile)
452
+ exit_code, output = awscli.exec("configure", "set", key, value, "--profile", profile)
446
453
  if exit_code:
447
454
  raise ExitError(exit_code, f"AWS CLI error: {output}")
448
455
 
449
456
 
450
- def _credentials_are_valid(profile):
457
+ @pass_runner
458
+ def _credentials_are_valid(awscli: Runner, profile: str):
451
459
  """Check if credentials for given profile are valid.
452
460
  If credentials are invalid, the command output will be as follows:
453
461
  Exit code:
@@ -462,12 +470,13 @@ def _credentials_are_valid(profile):
462
470
  Returns:
463
471
  bool: Whether the credentials are valid.
464
472
  """
465
- error_code, output = AWSCLI.exec("sts get-caller-identity", profile)
473
+ error_code, output = awscli.exec("sts", "get-caller-identity", "--profile", profile)
466
474
 
467
475
  return error_code != 255 and "InvalidClientTokenId" not in output
468
476
 
469
477
 
470
- def _get_management_account_id(profile):
478
+ @pass_runner
479
+ def _get_management_account_id(awscli: Runner, profile: str):
471
480
  """Get management account id through AWS cli.
472
481
 
473
482
  Args:
@@ -476,7 +485,7 @@ def _get_management_account_id(profile):
476
485
  Returns:
477
486
  str: Management account id.
478
487
  """
479
- exit_code, caller_identity = AWSCLI.exec("--output json sts get-caller-identity", profile)
488
+ exit_code, caller_identity = awscli.exec("sts", "get-caller-identity", "--output", "json", "--profile", profile)
480
489
  if exit_code:
481
490
  raise ExitError(exit_code, f"AWS CLI error: {caller_identity}")
482
491
 
@@ -484,7 +493,8 @@ def _get_management_account_id(profile):
484
493
  return caller_identity["Account"]
485
494
 
486
495
 
487
- def _get_organization_accounts(profile, project_name):
496
+ @pass_runner
497
+ def _get_organization_accounts(awscli: Runner, profile: str, project_name: str):
488
498
  """Get organization accounts names and ids. Removing the prefixed project name from the account names.
489
499
 
490
500
  Args:
@@ -494,7 +504,9 @@ def _get_organization_accounts(profile, project_name):
494
504
  Returns:
495
505
  dict: Mapping of organization accounts names to ids.
496
506
  """
497
- exit_code, organization_accounts = AWSCLI.exec("--output json organizations list-accounts", profile)
507
+ exit_code, organization_accounts = awscli.exec(
508
+ "organizations", "list-accounts", "--output", "json", "--profile", profile
509
+ )
498
510
 
499
511
  if exit_code:
500
512
  return {}
@@ -511,7 +523,8 @@ def _get_organization_accounts(profile, project_name):
511
523
  return accounts
512
524
 
513
525
 
514
- def _get_mfa_serial(profile):
526
+ @pass_runner
527
+ def _get_mfa_serial(awscli: Runner, profile: str):
515
528
  """Get MFA serial for the given profile credentials.
516
529
 
517
530
  Args:
@@ -520,7 +533,7 @@ def _get_mfa_serial(profile):
520
533
  Returns:
521
534
  str: MFA device serial.
522
535
  """
523
- exit_code, mfa_devices = AWSCLI.exec("--output json iam list-mfa-devices", profile)
536
+ exit_code, mfa_devices = awscli.exec("iam", "list-mfa-devices", "--output", "json", "--profile", profile)
524
537
  if exit_code:
525
538
  raise ExitError(exit_code, f"AWS CLI error: {mfa_devices}")
526
539
  mfa_devices = json.loads(mfa_devices)
@@ -538,7 +551,8 @@ def _get_mfa_serial(profile):
538
551
  )
539
552
 
540
553
 
541
- def configure_profile(profile, values):
554
+ @pass_runner
555
+ def configure_profile(awscli: Runner, profile: str, values: dict):
542
556
  """Set profile in `config` file for AWS cli.
543
557
 
544
558
  Args:
@@ -547,12 +561,20 @@ def configure_profile(profile, values):
547
561
  """
548
562
  logger.info(f"\tConfiguring profile [bold]{profile}[/bold]")
549
563
  for key, value in values.items():
550
- exit_code, output = AWSCLI.exec(f"configure set {key} {value}", profile)
564
+ exit_code, output = awscli.exec("configure", "set", key, value, "--profile", profile)
551
565
  if exit_code:
552
566
  raise ExitError(exit_code, f"AWS CLI error: {output}")
553
567
 
554
568
 
555
- def configure_accounts_profiles(profile, region, organization_accounts, project_accounts, fetch_mfa_device):
569
+ @pass_paths
570
+ def configure_accounts_profiles(
571
+ paths: PathsHandler,
572
+ profile: str,
573
+ region: str,
574
+ organization_accounts: dict,
575
+ project_accounts: list,
576
+ fetch_mfa_device: bool,
577
+ ):
556
578
  """Set up the required profiles for all accounts to be used with AWS cli. Backup previous profiles.
557
579
 
558
580
  Args:
@@ -595,16 +617,16 @@ def configure_accounts_profiles(profile, region, organization_accounts, project_
595
617
  account_profiles[f"{short_name}-{account_name}-{PROFILES[_type]['profile_role']}"] = account_profile
596
618
 
597
619
  logger.info("Backing up account profiles file.")
598
- _backup_file("config")
620
+ Path(paths.aws_config_file).copy(Path(paths.aws_config_file).with_suffix(".bkp"))
599
621
 
600
622
  for profile_identifier, profile_values in account_profiles.items():
601
623
  configure_profile(profile_identifier, profile_values)
602
624
 
603
625
 
604
- def _update_account_ids(config):
626
+ @pass_paths
627
+ def _update_account_ids(paths: PathsHandler, config: dict):
605
628
  """Update accounts ids in global configuration file.
606
- It updates both `[account name]_account_id` and `accounts` variables.
607
- This last one maintaning the format:
629
+ It updates `accounts` variables maintaining the format:
608
630
  ```
609
631
  account = {
610
632
  account_name = {
@@ -617,24 +639,15 @@ def _update_account_ids(config):
617
639
  Args:
618
640
  config (dict): Project configuration values.
619
641
  """
620
- if not PROJECT_COMMON_TFVARS.exists():
642
+ if not paths.common_tfvars.exists():
621
643
  return
622
644
 
623
- container_base_dir = f"/{config['project_name']}/config"
624
- container_common_tfvars_file = f"{container_base_dir}/{PROJECT_COMMON_TFVARS_FILE}"
625
-
626
645
  accs = []
627
646
  for account in config["organization"]["accounts"]:
628
647
  acc_name, acc_email, acc_id = account.values()
629
648
 
630
649
  acc = [f'\n email = "{acc_email}"']
631
650
  if acc_id:
632
- AWSCLI.system_exec(
633
- "hcledit "
634
- f"-f {container_common_tfvars_file} -u"
635
- f' attribute set {acc_name}_account_id "\\"{acc_id}\\""'
636
- )
637
-
638
651
  acc.append(f' id = "{acc_id}"')
639
652
  acc = ",\n".join(acc)
640
653
 
@@ -643,7 +656,11 @@ def _update_account_ids(config):
643
656
  accs = ",".join(accs)
644
657
  accs = f"{{{accs}\n}}"
645
658
 
646
- AWSCLI.system_exec("hcledit " f"-f {container_common_tfvars_file} -u" f" attribute set accounts '{accs}'")
659
+ common_tfvars = paths.common_tfvars.read_text()
660
+ common_tfvars = re.sub(
661
+ r"accounts\s*=\s*\{.*?\}(?=\s*(?:\n|$))", f"accounts = {accs}", common_tfvars, flags=re.DOTALL
662
+ )
663
+ paths.common_tfvars.write_text(common_tfvars)
647
664
 
648
665
 
649
666
  def mutually_exclusive(context, param, value):