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.
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/PKG-INFO +2 -3
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/__init__.py +1 -1
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/_utils.py +7 -67
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/leverage.py +6 -13
- leverage-2.2.0rc3/leverage/modules/__init__.py +7 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/auth.py +0 -1
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/aws.py +18 -3
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/credentials.py +82 -65
- leverage-2.2.0rc3/leverage/modules/kubectl.py +188 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/project.py +14 -12
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/tf.py +3 -3
- leverage-2.2.0rc3/leverage/modules/tfautomv.py +43 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/tfrunner.py +19 -5
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/utils.py +10 -4
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/path.py +10 -2
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/pyproject.toml +2 -4
- leverage-2.2.0rc1/leverage/container.py +0 -716
- leverage-2.2.0rc1/leverage/containers/kubectl.py +0 -145
- leverage-2.2.0rc1/leverage/modules/__init__.py +0 -9
- leverage-2.2.0rc1/leverage/modules/kubectl.py +0 -43
- leverage-2.2.0rc1/leverage/modules/tfautomv.py +0 -39
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/LICENSE.txt +0 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/README.md +0 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/_backend_config.py +0 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/_internals.py +0 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/_parsing.py +0 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/conf.py +0 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/logger.py +0 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/run.py +0 -0
- {leverage-2.2.0rc1 → leverage-2.2.0rc3}/leverage/modules/runner.py +0 -0
- {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.
|
|
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)
|
|
@@ -8,10 +8,8 @@ from typing import List, Optional
|
|
|
8
8
|
|
|
9
9
|
import hcl2
|
|
10
10
|
import lark
|
|
11
|
-
from click.exceptions import
|
|
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
|
|
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
|
-
|
|
116
|
-
super(ExitError, self).__init__(
|
|
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
|
-
|
|
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
|
-
|
|
39
|
+
leverage.add_command(credentials)
|
|
47
40
|
leverage.add_command(aws)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
leverage.add_command(tfautomv)
|
|
42
|
+
leverage.add_command(kubectl)
|
|
43
|
+
leverage.add_command(kubectl, name="kc")
|
|
@@ -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
|
|
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.
|
|
21
|
-
from leverage.
|
|
22
|
-
from leverage.path import
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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 =
|
|
319
|
+
env_config = state.config
|
|
310
320
|
|
|
311
321
|
terraform_config = {}
|
|
312
322
|
logger.info("Loading tf common configuration.")
|
|
313
|
-
terraform_config =
|
|
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
|
|
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
|
-
|
|
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, _ =
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
626
|
+
@pass_paths
|
|
627
|
+
def _update_account_ids(paths: PathsHandler, config: dict):
|
|
605
628
|
"""Update accounts ids in global configuration file.
|
|
606
|
-
It updates
|
|
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
|
|
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
|
-
|
|
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):
|