leverage 2.1.2rc1__tar.gz → 2.2.0rc0__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.1.2rc1 → leverage-2.2.0rc0}/PKG-INFO +2 -1
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/__init__.py +1 -7
- leverage-2.2.0rc0/leverage/_backend_config.py +172 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/_internals.py +26 -1
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/_utils.py +4 -4
- leverage-2.2.0rc0/leverage/leverage.py +50 -0
- leverage-2.2.0rc0/leverage/modules/__init__.py +9 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/modules/auth.py +61 -14
- leverage-2.2.0rc0/leverage/modules/aws.py +338 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/modules/credentials.py +0 -3
- leverage-2.2.0rc0/leverage/modules/runner.py +124 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/modules/tf.py +178 -181
- leverage-2.2.0rc0/leverage/modules/tfrunner.py +72 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/modules/utils.py +12 -15
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/path.py +14 -59
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/pyproject.toml +3 -1
- leverage-2.1.2rc1/leverage/leverage.py +0 -68
- leverage-2.1.2rc1/leverage/modules/__init__.py +0 -6
- leverage-2.1.2rc1/leverage/modules/aws.py +0 -225
- leverage-2.1.2rc1/leverage/modules/shell.py +0 -44
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/LICENSE.txt +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/README.md +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/_parsing.py +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/conf.py +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/container.py +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/containers/kubectl.py +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/logger.py +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/modules/kubectl.py +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/modules/project.py +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/modules/run.py +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/modules/tfautomv.py +0 -0
- {leverage-2.1.2rc1 → leverage-2.2.0rc0}/leverage/tasks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: leverage
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0rc0
|
|
4
4
|
Summary: Binbash Leverage Command-Line tool.
|
|
5
5
|
Home-page: https://github.com/binbashar/leverage
|
|
6
6
|
License: MIT
|
|
@@ -25,6 +25,7 @@ Requires-Dist: docker (==6.1.0)
|
|
|
25
25
|
Requires-Dist: dockerpty (==0.4.1)
|
|
26
26
|
Requires-Dist: docutils (==0.17.1)
|
|
27
27
|
Requires-Dist: jinja2 (==3.0.1)
|
|
28
|
+
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
28
29
|
Requires-Dist: python-hcl2 (==7.3.1)
|
|
29
30
|
Requires-Dist: questionary (==1.10.0)
|
|
30
31
|
Requires-Dist: requests (==2.31)
|
|
@@ -4,13 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# pylint: disable=wrong-import-position
|
|
6
6
|
|
|
7
|
-
__version__ = "2.
|
|
8
|
-
__toolbox_version__ = "1.3.5-0.2.0"
|
|
9
|
-
|
|
10
|
-
MINIMUM_VERSIONS = {
|
|
11
|
-
"TERRAFORM": "1.3.5",
|
|
12
|
-
"TOOLBOX": "0.2.4",
|
|
13
|
-
}
|
|
7
|
+
__version__ = "2.2.0rc0"
|
|
14
8
|
|
|
15
9
|
import sys
|
|
16
10
|
from shutil import which
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Utilities for modifying Terraform backend configuration."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Union, Optional
|
|
6
|
+
|
|
7
|
+
import hcl2
|
|
8
|
+
import lark
|
|
9
|
+
|
|
10
|
+
from leverage._utils import ExitError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def set_backend_key(config_file_path: Union[str, Path], key: str) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Set or update the backend key in a Terraform config.tf file.
|
|
16
|
+
|
|
17
|
+
This function modifies the Terraform backend configuration to set the S3 state key.
|
|
18
|
+
It preserves all comments, formatting, and other HCL code in the file by using
|
|
19
|
+
string manipulation to surgically modify only the key attribute.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
config_file_path: Path to the config.tf file
|
|
23
|
+
key: The backend key value to set (e.g., "apps-devstg/notifications/terraform.tfstate")
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ExitError: If the file cannot be found, cannot be parsed, or does not contain a
|
|
27
|
+
terraform block with an S3 backend
|
|
28
|
+
"""
|
|
29
|
+
config_file_path = Path(config_file_path)
|
|
30
|
+
|
|
31
|
+
# Validate file exists
|
|
32
|
+
if not config_file_path.exists():
|
|
33
|
+
raise ExitError(1, f"Config file not found: {config_file_path}")
|
|
34
|
+
|
|
35
|
+
# Read the file content
|
|
36
|
+
content = config_file_path.read_text()
|
|
37
|
+
|
|
38
|
+
# Parse the config file to validate structure
|
|
39
|
+
try:
|
|
40
|
+
config_tf = hcl2.loads(content)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise ExitError(1, f"Failed to parse config.tf: {e}")
|
|
43
|
+
|
|
44
|
+
# Validate that the file contains a terraform backend block with S3
|
|
45
|
+
if not (
|
|
46
|
+
"terraform" in config_tf
|
|
47
|
+
and config_tf["terraform"]
|
|
48
|
+
and isinstance(config_tf["terraform"], list)
|
|
49
|
+
and "backend" in config_tf["terraform"][0]
|
|
50
|
+
and config_tf["terraform"][0]["backend"]
|
|
51
|
+
and isinstance(config_tf["terraform"][0]["backend"], list)
|
|
52
|
+
and "s3" in config_tf["terraform"][0]["backend"][0]
|
|
53
|
+
):
|
|
54
|
+
raise ExitError(
|
|
55
|
+
1,
|
|
56
|
+
f"Malformed config.tf: File must contain a terraform block with an S3 backend. "
|
|
57
|
+
f"Expected structure:\n"
|
|
58
|
+
f"terraform {{\n"
|
|
59
|
+
f' backend "s3" {{\n'
|
|
60
|
+
f" # configuration\n"
|
|
61
|
+
f" }}\n"
|
|
62
|
+
f"}}",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Check if key already exists
|
|
66
|
+
backend_config = config_tf["terraform"][0]["backend"][0]["s3"]
|
|
67
|
+
key_exists = "key" in backend_config
|
|
68
|
+
|
|
69
|
+
# Modify the file content
|
|
70
|
+
modified_content = _modify_backend_key(content, key, key_exists)
|
|
71
|
+
|
|
72
|
+
# Write back to file
|
|
73
|
+
config_file_path.write_text(modified_content)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _modify_backend_key(content: str, key: str, key_exists: bool) -> str:
|
|
77
|
+
"""
|
|
78
|
+
Modify the backend key in the HCL content.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
content: The original file content
|
|
82
|
+
key: The new key value
|
|
83
|
+
key_exists: Whether the key attribute already exists
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Modified content with the key set
|
|
87
|
+
"""
|
|
88
|
+
# Pattern to find the backend "s3" block
|
|
89
|
+
# This matches: backend "s3" { ... }
|
|
90
|
+
backend_pattern = r'(backend\s+"s3"\s*\{)'
|
|
91
|
+
|
|
92
|
+
if key_exists:
|
|
93
|
+
# Update existing key
|
|
94
|
+
# Match: key = "anything" or key = "anything" with various whitespace/quotes
|
|
95
|
+
key_pattern = r'(\s*key\s*=\s*)"[^"]*"'
|
|
96
|
+
replacement = r'\1"' + key + '"'
|
|
97
|
+
|
|
98
|
+
modified = re.sub(key_pattern, replacement, content)
|
|
99
|
+
|
|
100
|
+
return modified
|
|
101
|
+
else:
|
|
102
|
+
# Add new key attribute
|
|
103
|
+
# Find the backend "s3" block and add the key after the opening brace
|
|
104
|
+
def add_key(match):
|
|
105
|
+
# Get the matched backend opening
|
|
106
|
+
backend_opening = match.group(1)
|
|
107
|
+
|
|
108
|
+
# Find the indentation by looking at the next line
|
|
109
|
+
remaining = content[match.end() :]
|
|
110
|
+
next_line_match = re.search(r"\n(\s*)", remaining)
|
|
111
|
+
if next_line_match:
|
|
112
|
+
indent = next_line_match.group(1)
|
|
113
|
+
else:
|
|
114
|
+
indent = " " # Default to 4 spaces
|
|
115
|
+
|
|
116
|
+
# Add the key attribute with proper indentation
|
|
117
|
+
return f'{backend_opening}\n{indent}key = "{key}"'
|
|
118
|
+
|
|
119
|
+
modified = re.sub(backend_pattern, add_key, content, count=1)
|
|
120
|
+
|
|
121
|
+
return modified
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_backend_key(config_file: Union[str, Path]) -> Optional[str]:
|
|
125
|
+
"""
|
|
126
|
+
Get the current backend key from a Terraform config.tf file.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
config_file: Path to the config.tf file
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The backend key if it exists, None otherwise
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
>>> get_backend_key("/path/to/config.tf")
|
|
136
|
+
'apps-devstg/layer/terraform.tfstate'
|
|
137
|
+
"""
|
|
138
|
+
config_file = Path(config_file)
|
|
139
|
+
|
|
140
|
+
if not config_file.exists():
|
|
141
|
+
raise ExitError(1, f"Config file not found: {config_file}")
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
config_content = config_file.read_text()
|
|
145
|
+
config_tf = hcl2.loads(config_content)
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
"terraform" in config_tf
|
|
149
|
+
and config_tf["terraform"]
|
|
150
|
+
and isinstance(config_tf["terraform"], list)
|
|
151
|
+
and "backend" in config_tf["terraform"][0]
|
|
152
|
+
and config_tf["terraform"][0]["backend"]
|
|
153
|
+
and isinstance(config_tf["terraform"][0]["backend"], list)
|
|
154
|
+
and "s3" in config_tf["terraform"][0]["backend"][0]
|
|
155
|
+
):
|
|
156
|
+
return config_tf["terraform"][0]["backend"][0]["s3"].get("key")
|
|
157
|
+
else:
|
|
158
|
+
raise ExitError(
|
|
159
|
+
1,
|
|
160
|
+
f"Malformed [bold]config.tf[/bold] file. Missing backend block.\n"
|
|
161
|
+
f"In some cases you may want to skip this check by using the --skip-validation flag, "
|
|
162
|
+
f"e.g. the first time you initialize a tf-backend layer.",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
except lark.exceptions.UnexpectedInput as error:
|
|
166
|
+
raise ExitError(
|
|
167
|
+
1,
|
|
168
|
+
f"Possible invalid expression in [bold]config.tf[/bold] near line {error.line}, column {error.column}\n"
|
|
169
|
+
f"{error.get_context(config_content)}",
|
|
170
|
+
)
|
|
171
|
+
except Exception:
|
|
172
|
+
raise ExitError(1, f"Malformed [bold]config.tf[/bold] file. Unable to parse.")
|
|
@@ -27,7 +27,9 @@ class State:
|
|
|
27
27
|
def __init__(self):
|
|
28
28
|
self._verbosity = None
|
|
29
29
|
self.module = Module()
|
|
30
|
-
self.
|
|
30
|
+
self.config = None
|
|
31
|
+
self.paths = None
|
|
32
|
+
self.runner = None
|
|
31
33
|
|
|
32
34
|
@property
|
|
33
35
|
def verbosity(self):
|
|
@@ -51,3 +53,26 @@ def pass_container(command):
|
|
|
51
53
|
return command(ctx.obj.container, *args, **kwargs)
|
|
52
54
|
|
|
53
55
|
return new_command
|
|
56
|
+
|
|
57
|
+
def pass_runner(command):
|
|
58
|
+
"""Decorator to pass the current runner (Terraform/OpenTofu runner) to the command."""
|
|
59
|
+
|
|
60
|
+
@wraps(command)
|
|
61
|
+
def new_command(*args, **kwargs):
|
|
62
|
+
ctx = click.get_current_context()
|
|
63
|
+
|
|
64
|
+
return command(ctx.obj.runner, *args, **kwargs)
|
|
65
|
+
|
|
66
|
+
return new_command
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def pass_paths(command):
|
|
70
|
+
"""Decorator to pass the current project paths to the command."""
|
|
71
|
+
|
|
72
|
+
@wraps(command)
|
|
73
|
+
def new_command(*args, **kwargs):
|
|
74
|
+
ctx = click.get_current_context()
|
|
75
|
+
|
|
76
|
+
return command(ctx.obj.paths, *args, **kwargs)
|
|
77
|
+
|
|
78
|
+
return new_command
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from subprocess import run
|
|
7
|
-
from
|
|
6
|
+
from subprocess import PIPE, run
|
|
7
|
+
from typing import List, Optional
|
|
8
8
|
|
|
9
9
|
import hcl2
|
|
10
10
|
import lark
|
|
@@ -153,12 +153,12 @@ class ContainerSession:
|
|
|
153
153
|
self.docker_client.api.remove_container(self.container_data)
|
|
154
154
|
|
|
155
155
|
|
|
156
|
-
def key_finder(d: dict, target: str, avoid: str = None):
|
|
156
|
+
def key_finder(d: dict, target: str, avoid: Optional[str] = None) -> List[str]:
|
|
157
157
|
"""
|
|
158
158
|
Iterate over a dict of dicts and/or lists of dicts, looking for a key with value "target".
|
|
159
159
|
Collect and return all the values that matches "target" as key.
|
|
160
160
|
"""
|
|
161
|
-
values = []
|
|
161
|
+
values: List[str] = []
|
|
162
162
|
|
|
163
163
|
for key, value in d.items():
|
|
164
164
|
if isinstance(value, dict):
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Binbash Leverage Command-line tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import rich
|
|
6
|
+
from packaging.version import Version
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from leverage import __version__, conf
|
|
11
|
+
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
|
+
from leverage.path import NotARepositoryError, PathsHandler
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.group(invoke_without_command=True)
|
|
21
|
+
@click.option("-v", "--verbose", is_flag=True, help="Increase output verbosity.")
|
|
22
|
+
@click.version_option(version=__version__)
|
|
23
|
+
@pass_state
|
|
24
|
+
@click.pass_context
|
|
25
|
+
def leverage(context, state, verbose):
|
|
26
|
+
"""Leverage Reference Architecture projects command-line tool."""
|
|
27
|
+
# --verbose | -v
|
|
28
|
+
state.verbosity = verbose
|
|
29
|
+
if context.invoked_subcommand is None:
|
|
30
|
+
# leverage called with no subcommand
|
|
31
|
+
click.echo(context.get_help())
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
state.config = conf.load()
|
|
35
|
+
except NotARepositoryError:
|
|
36
|
+
return
|
|
37
|
+
state.paths = PathsHandler(state.config)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Add modules to leverage
|
|
41
|
+
leverage.add_command(run)
|
|
42
|
+
# leverage.add_command(project)
|
|
43
|
+
leverage.add_command(tofu)
|
|
44
|
+
leverage.add_command(tofu, name="tf")
|
|
45
|
+
leverage.add_command(terraform)
|
|
46
|
+
# leverage.add_command(credentials)
|
|
47
|
+
leverage.add_command(aws)
|
|
48
|
+
# leverage.add_command(tfautomv)
|
|
49
|
+
# leverage.add_command(kubectl)
|
|
50
|
+
# leverage.add_command(kubectl, name="kc")
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import time
|
|
2
|
+
import json
|
|
2
3
|
from pathlib import Path
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from dateutil.tz import tzutc
|
|
3
6
|
from configparser import NoSectionError, NoOptionError
|
|
4
7
|
|
|
5
8
|
import boto3
|
|
@@ -7,6 +10,7 @@ from botocore.exceptions import ClientError
|
|
|
7
10
|
from configupdater import ConfigUpdater
|
|
8
11
|
|
|
9
12
|
from leverage import logger
|
|
13
|
+
from leverage.path import PathsHandler
|
|
10
14
|
from leverage._utils import key_finder, ExitError, get_or_create_section, parse_tf_file
|
|
11
15
|
|
|
12
16
|
|
|
@@ -21,10 +25,14 @@ def get_layer_profile(raw_profile: str, config_updater: ConfigUpdater, tf_profil
|
|
|
21
25
|
raise SkipProfile
|
|
22
26
|
|
|
23
27
|
# if it is exactly that variable, we already know the layer profile is tf_profile
|
|
24
|
-
layer_profile = tf_profile if raw_profile
|
|
28
|
+
layer_profile = tf_profile if raw_profile in ("${var.profile}", "each.value.profile") else None
|
|
25
29
|
|
|
26
30
|
# replace variables with their corresponding values
|
|
27
|
-
raw =
|
|
31
|
+
raw = (
|
|
32
|
+
raw_profile.replace("${var.profile}", tf_profile)
|
|
33
|
+
.replace("${var.project}", project)
|
|
34
|
+
.replace("each.value.profile", tf_profile)
|
|
35
|
+
)
|
|
28
36
|
|
|
29
37
|
# the project and the role are at the beginning and end of the string
|
|
30
38
|
_, *account_name, _ = raw.split("-")
|
|
@@ -57,7 +65,7 @@ def update_config_section(updater: ConfigUpdater, layer_profile: str, data: dict
|
|
|
57
65
|
updater.update_file()
|
|
58
66
|
|
|
59
67
|
|
|
60
|
-
def get_profiles(
|
|
68
|
+
def get_profiles(paths: PathsHandler):
|
|
61
69
|
"""
|
|
62
70
|
Get the AWS profiles present on the layer by parsing some tf files.
|
|
63
71
|
"""
|
|
@@ -65,7 +73,7 @@ def get_profiles(cli):
|
|
|
65
73
|
# these are files from the layer we are currently on
|
|
66
74
|
for name in ("config.tf", "locals.tf", "runtime.tf"):
|
|
67
75
|
try:
|
|
68
|
-
tf_config = parse_tf_file(Path(
|
|
76
|
+
tf_config = parse_tf_file(Path(paths.cwd / name))
|
|
69
77
|
except FileNotFoundError:
|
|
70
78
|
continue
|
|
71
79
|
|
|
@@ -74,25 +82,65 @@ def get_profiles(cli):
|
|
|
74
82
|
raw_profiles.update(set(key_finder(tf_config, "profile", "lookup")))
|
|
75
83
|
|
|
76
84
|
# the profile value from <layer>/config/backend.tfvars
|
|
77
|
-
backend_config = parse_tf_file(
|
|
85
|
+
backend_config = parse_tf_file(paths.backend_tfvars)
|
|
78
86
|
tf_profile = backend_config["profile"]
|
|
79
87
|
|
|
80
88
|
return tf_profile, raw_profiles
|
|
81
89
|
|
|
82
90
|
|
|
83
|
-
def
|
|
84
|
-
|
|
91
|
+
def get_sso_access_token(sso_token_file: Path) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Get the SSO access token from the token file.
|
|
94
|
+
"""
|
|
95
|
+
return json.loads(sso_token_file.read_text())["accessToken"]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def check_sso_token(paths: PathsHandler):
|
|
99
|
+
"""Check for the existence and validity of the SSO token to be used to get credentials."""
|
|
100
|
+
|
|
101
|
+
# Adding `token` file name to this function in order to
|
|
102
|
+
# meet the requirement regarding to have just one
|
|
103
|
+
# token file in the sso/cache
|
|
104
|
+
sso_role = paths.account_conf.get("sso_role")
|
|
105
|
+
token_file = paths.sso_cache / sso_role
|
|
106
|
+
|
|
107
|
+
token_files = list(paths.sso_cache.glob("*"))
|
|
108
|
+
if not token_files:
|
|
109
|
+
raise ExitError(1, "No AWS SSO token found. Please log in or configure SSO.")
|
|
110
|
+
|
|
111
|
+
if token_file not in token_files and paths.sso_token_file not in token_files:
|
|
112
|
+
raise ExitError(
|
|
113
|
+
1,
|
|
114
|
+
"No valid AWS SSO token found for current account.\n"
|
|
115
|
+
"Please log out and reconfigure SSO before proceeding.",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
token = json.loads(paths.sso_token_file.read_text())
|
|
119
|
+
expiry = datetime.strptime(token.get("expiresAt"), "%Y-%m-%dT%H:%M:%SZ")
|
|
120
|
+
renewal = datetime.now()
|
|
121
|
+
|
|
122
|
+
if expiry < renewal:
|
|
123
|
+
raise ExitError(
|
|
124
|
+
1,
|
|
125
|
+
"AWS SSO token has expired, please log back in by running [bold]leverage aws sso login[/bold]"
|
|
126
|
+
" to refresh your credentials before re-running the last command.",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def refresh_layer_credentials(paths: PathsHandler):
|
|
131
|
+
tf_profile, raw_profiles = get_profiles(paths)
|
|
85
132
|
config_updater = ConfigUpdater()
|
|
86
|
-
config_updater.read(
|
|
133
|
+
config_updater.read(paths.aws_config_file)
|
|
87
134
|
|
|
88
|
-
|
|
135
|
+
region = config_updater.get(f"profile {paths.project}-sso", "sso_region").value
|
|
136
|
+
client = boto3.client("sso", region_name=region)
|
|
89
137
|
for raw in raw_profiles:
|
|
90
138
|
try:
|
|
91
139
|
account_id, account_name, sso_role, layer_profile = get_layer_profile(
|
|
92
140
|
raw,
|
|
93
141
|
config_updater,
|
|
94
142
|
tf_profile,
|
|
95
|
-
|
|
143
|
+
paths.project,
|
|
96
144
|
)
|
|
97
145
|
except SkipProfile:
|
|
98
146
|
continue
|
|
@@ -119,7 +167,7 @@ def refresh_layer_credentials(cli):
|
|
|
119
167
|
credentials = client.get_role_credentials(
|
|
120
168
|
roleName=sso_role,
|
|
121
169
|
accountId=account_id,
|
|
122
|
-
accessToken=
|
|
170
|
+
accessToken=get_sso_access_token(paths.sso_token_file),
|
|
123
171
|
)["roleCredentials"]
|
|
124
172
|
except ClientError as error:
|
|
125
173
|
if error.response["Error"]["Code"] in ("AccessDeniedException", "ForbiddenException"):
|
|
@@ -140,10 +188,9 @@ def refresh_layer_credentials(cli):
|
|
|
140
188
|
},
|
|
141
189
|
)
|
|
142
190
|
# write credentials on aws/<project>/credentials (create the file if it doesn't exist first)
|
|
143
|
-
|
|
144
|
-
creds_path.touch(exist_ok=True)
|
|
191
|
+
paths.aws_credentials_file.touch(exist_ok=True)
|
|
145
192
|
credentials_updater = ConfigUpdater()
|
|
146
|
-
credentials_updater.read(
|
|
193
|
+
credentials_updater.read(paths.aws_credentials_file)
|
|
147
194
|
|
|
148
195
|
update_config_section(
|
|
149
196
|
credentials_updater,
|