leverage 2.2.0rc2__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 (33) hide show
  1. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/PKG-INFO +3 -3
  2. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/__init__.py +1 -7
  3. leverage-2.2.0rc3/leverage/_backend_config.py +172 -0
  4. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/_internals.py +18 -4
  5. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/_utils.py +11 -71
  6. leverage-2.2.0rc3/leverage/leverage.py +43 -0
  7. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/modules/__init__.py +2 -1
  8. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/modules/auth.py +63 -15
  9. leverage-2.2.0rc3/leverage/modules/aws.py +353 -0
  10. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/modules/credentials.py +82 -68
  11. leverage-2.2.0rc3/leverage/modules/kubectl.py +188 -0
  12. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/modules/project.py +14 -12
  13. leverage-2.2.0rc3/leverage/modules/runner.py +124 -0
  14. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/modules/tf.py +177 -180
  15. leverage-2.2.0rc3/leverage/modules/tfautomv.py +43 -0
  16. leverage-2.2.0rc3/leverage/modules/tfrunner.py +86 -0
  17. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/modules/utils.py +19 -16
  18. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/path.py +24 -61
  19. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/pyproject.toml +4 -4
  20. leverage-2.2.0rc2/leverage/container.py +0 -716
  21. leverage-2.2.0rc2/leverage/containers/kubectl.py +0 -145
  22. leverage-2.2.0rc2/leverage/leverage.py +0 -68
  23. leverage-2.2.0rc2/leverage/modules/aws.py +0 -225
  24. leverage-2.2.0rc2/leverage/modules/kubectl.py +0 -43
  25. leverage-2.2.0rc2/leverage/modules/shell.py +0 -44
  26. leverage-2.2.0rc2/leverage/modules/tfautomv.py +0 -39
  27. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/LICENSE.txt +0 -0
  28. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/README.md +0 -0
  29. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/_parsing.py +0 -0
  30. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/conf.py +0 -0
  31. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/logger.py +0 -0
  32. {leverage-2.2.0rc2 → leverage-2.2.0rc3}/leverage/modules/run.py +0 -0
  33. {leverage-2.2.0rc2 → 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.0rc2
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,10 @@ 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)
27
+ Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
28
28
  Requires-Dist: python-hcl2 (==7.3.1)
29
29
  Requires-Dist: questionary (==1.10.0)
30
30
  Requires-Dist: requests (==2.31)
@@ -4,13 +4,7 @@
4
4
 
5
5
  # pylint: disable=wrong-import-position
6
6
 
7
- __version__ = "2.2.0rc2"
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.0rc3"
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.container = None
30
+ self.config = None
31
+ self.paths = None
32
+ self.runner = None
31
33
 
32
34
  @property
33
35
  def verbosity(self):
@@ -41,13 +43,25 @@ class State:
41
43
  pass_state = click.make_pass_decorator(State, ensure=True)
42
44
 
43
45
 
44
- def pass_container(command):
45
- """Decorator to pass the current container to the command."""
46
+ def pass_runner(command):
47
+ """Decorator to pass the current runner (Terraform/OpenTofu runner) to the command."""
46
48
 
47
49
  @wraps(command)
48
50
  def new_command(*args, **kwargs):
49
51
  ctx = click.get_current_context()
50
52
 
51
- return command(ctx.obj.container, *args, **kwargs)
53
+ return command(ctx.obj.runner, *args, **kwargs)
54
+
55
+ return new_command
56
+
57
+
58
+ def pass_paths(command):
59
+ """Decorator to pass the current project paths to the command."""
60
+
61
+ @wraps(command)
62
+ def new_command(*args, **kwargs):
63
+ ctx = click.get_current_context()
64
+
65
+ return command(ctx.obj.paths, *args, **kwargs)
52
66
 
53
67
  return new_command
@@ -3,15 +3,13 @@
3
3
  """
4
4
 
5
5
  from pathlib import Path
6
- from subprocess import run
7
- from subprocess import PIPE
6
+ from subprocess import PIPE, run
7
+ 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,31 +93,12 @@ 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
- def key_finder(d: dict, target: str, avoid: str = None):
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".
159
99
  Collect and return all the values that matches "target" as key.
160
100
  """
161
- values = []
101
+ values: List[str] = []
162
102
 
163
103
  for key, value in d.items():
164
104
  if isinstance(value, dict):
@@ -0,0 +1,43 @@
1
+ """
2
+ Binbash Leverage Command-line tool.
3
+ """
4
+
5
+ import click
6
+
7
+ from leverage import __version__, conf
8
+ from leverage._internals import pass_state
9
+ from leverage.path import NotARepositoryError, PathsHandler
10
+ from leverage.modules import aws, credentials, run, project, tofu, terraform, tfautomv, kubectl
11
+
12
+
13
+ @click.group(invoke_without_command=True)
14
+ @click.option("-v", "--verbose", is_flag=True, help="Increase output verbosity.")
15
+ @click.version_option(version=__version__)
16
+ @pass_state
17
+ @click.pass_context
18
+ def leverage(context, state, verbose):
19
+ """Leverage Reference Architecture projects command-line tool."""
20
+ # --verbose | -v
21
+ state.verbosity = verbose
22
+ if context.invoked_subcommand is None:
23
+ # leverage called with no subcommand
24
+ click.echo(context.get_help())
25
+
26
+ try:
27
+ state.config = conf.load()
28
+ except NotARepositoryError:
29
+ return
30
+ state.paths = PathsHandler(state.config)
31
+
32
+
33
+ # Add modules to leverage
34
+ leverage.add_command(run)
35
+ leverage.add_command(project)
36
+ leverage.add_command(tofu)
37
+ leverage.add_command(tofu, name="tf")
38
+ leverage.add_command(terraform)
39
+ leverage.add_command(credentials)
40
+ leverage.add_command(aws)
41
+ leverage.add_command(tfautomv)
42
+ leverage.add_command(kubectl)
43
+ leverage.add_command(kubectl, name="kc")
@@ -1,6 +1,7 @@
1
1
  from .run import run
2
2
  from .project import project
3
+ from .aws import aws
3
4
  from .tf import tofu, terraform
5
+ from .credentials import credentials
4
6
  from .tfautomv import tfautomv
5
7
  from .kubectl import kubectl
6
- from .shell import shell
@@ -1,5 +1,7 @@
1
1
  import time
2
+ import json
2
3
  from pathlib import Path
4
+ from datetime import datetime
3
5
  from configparser import NoSectionError, NoOptionError
4
6
 
5
7
  import boto3
@@ -7,6 +9,7 @@ from botocore.exceptions import ClientError
7
9
  from configupdater import ConfigUpdater
8
10
 
9
11
  from leverage import logger
12
+ from leverage.path import PathsHandler
10
13
  from leverage._utils import key_finder, ExitError, get_or_create_section, parse_tf_file
11
14
 
12
15
 
@@ -21,10 +24,14 @@ def get_layer_profile(raw_profile: str, config_updater: ConfigUpdater, tf_profil
21
24
  raise SkipProfile
22
25
 
23
26
  # if it is exactly that variable, we already know the layer profile is tf_profile
24
- layer_profile = tf_profile if raw_profile == "${var.profile}" else None
27
+ layer_profile = tf_profile if raw_profile in ("${var.profile}", "each.value.profile") else None
25
28
 
26
29
  # replace variables with their corresponding values
27
- raw = raw_profile.replace("${var.profile}", tf_profile).replace("${var.project}", project)
30
+ raw = (
31
+ raw_profile.replace("${var.profile}", tf_profile)
32
+ .replace("${var.project}", project)
33
+ .replace("each.value.profile", tf_profile)
34
+ )
28
35
 
29
36
  # the project and the role are at the beginning and end of the string
30
37
  _, *account_name, _ = raw.split("-")
@@ -57,15 +64,15 @@ def update_config_section(updater: ConfigUpdater, layer_profile: str, data: dict
57
64
  updater.update_file()
58
65
 
59
66
 
60
- def get_profiles(cli):
67
+ def get_profiles(paths: PathsHandler):
61
68
  """
62
69
  Get the AWS profiles present on the layer by parsing some tf files.
63
70
  """
64
71
  raw_profiles = set()
65
72
  # these are files from the layer we are currently on
66
- for name in ("config.tf", "locals.tf"):
73
+ for name in ("config.tf", "locals.tf", "runtime.tf"):
67
74
  try:
68
- tf_config = parse_tf_file(Path(cli.paths.cwd / name))
75
+ tf_config = parse_tf_file(Path(paths.cwd / name))
69
76
  except FileNotFoundError:
70
77
  continue
71
78
 
@@ -74,25 +81,65 @@ def get_profiles(cli):
74
81
  raw_profiles.update(set(key_finder(tf_config, "profile", "lookup")))
75
82
 
76
83
  # the profile value from <layer>/config/backend.tfvars
77
- backend_config = parse_tf_file(cli.paths.local_backend_tfvars)
84
+ backend_config = parse_tf_file(paths.backend_tfvars)
78
85
  tf_profile = backend_config["profile"]
79
86
 
80
87
  return tf_profile, raw_profiles
81
88
 
82
89
 
83
- def refresh_layer_credentials(cli):
84
- tf_profile, raw_profiles = get_profiles(cli)
90
+ def get_sso_access_token(sso_token_file: Path) -> str:
91
+ """
92
+ Get the SSO access token from the token file.
93
+ """
94
+ return json.loads(sso_token_file.read_text())["accessToken"]
95
+
96
+
97
+ def check_sso_token(paths: PathsHandler):
98
+ """Check for the existence and validity of the SSO token to be used to get credentials."""
99
+
100
+ # Adding `token` file name to this function in order to
101
+ # meet the requirement regarding to have just one
102
+ # token file in the sso/cache
103
+ sso_role = paths.account_conf.get("sso_role")
104
+ token_file = paths.sso_cache / sso_role
105
+
106
+ token_files = list(paths.sso_cache.glob("*"))
107
+ if not token_files:
108
+ raise ExitError(1, "No AWS SSO token found. Please log in or configure SSO.")
109
+
110
+ if token_file not in token_files and paths.sso_token_file not in token_files:
111
+ raise ExitError(
112
+ 1,
113
+ "No valid AWS SSO token found for current account.\n"
114
+ "Please log out and reconfigure SSO before proceeding.",
115
+ )
116
+
117
+ token = json.loads(paths.sso_token_file.read_text())
118
+ expiry = datetime.strptime(token.get("expiresAt"), "%Y-%m-%dT%H:%M:%SZ")
119
+ renewal = datetime.now()
120
+
121
+ if expiry < renewal:
122
+ raise ExitError(
123
+ 1,
124
+ "AWS SSO token has expired, please log back in by running [bold]leverage aws sso login[/bold]"
125
+ " to refresh your credentials before re-running the last command.",
126
+ )
127
+
128
+
129
+ def refresh_layer_credentials(paths: PathsHandler):
130
+ tf_profile, raw_profiles = get_profiles(paths)
85
131
  config_updater = ConfigUpdater()
86
- config_updater.read(cli.paths.host_aws_profiles_file)
132
+ config_updater.read(paths.aws_config_file)
87
133
 
88
- client = boto3.client("sso", region_name=cli.sso_region_from_main_profile)
134
+ region = config_updater.get(f"profile {paths.project}-sso", "sso_region").value
135
+ client = boto3.client("sso", region_name=region)
89
136
  for raw in raw_profiles:
90
137
  try:
91
138
  account_id, account_name, sso_role, layer_profile = get_layer_profile(
92
139
  raw,
93
140
  config_updater,
94
141
  tf_profile,
95
- cli.project,
142
+ paths.project,
96
143
  )
97
144
  except SkipProfile:
98
145
  continue
@@ -119,7 +166,7 @@ def refresh_layer_credentials(cli):
119
166
  credentials = client.get_role_credentials(
120
167
  roleName=sso_role,
121
168
  accountId=account_id,
122
- accessToken=cli.get_sso_access_token(),
169
+ accessToken=get_sso_access_token(paths.sso_token_file),
123
170
  )["roleCredentials"]
124
171
  except ClientError as error:
125
172
  if error.response["Error"]["Code"] in ("AccessDeniedException", "ForbiddenException"):
@@ -129,6 +176,8 @@ def refresh_layer_credentials(cli):
129
176
  " in this account.\nPlease check with your administrator or try"
130
177
  " running [bold]leverage aws configure sso[/bold].",
131
178
  )
179
+ else:
180
+ raise ExitError(50, f"Error retrieving role credentials: {error}")
132
181
 
133
182
  # update expiration on aws/<project>/config
134
183
  logger.info(f"Writing {layer_profile} profile")
@@ -140,10 +189,9 @@ def refresh_layer_credentials(cli):
140
189
  },
141
190
  )
142
191
  # write credentials on aws/<project>/credentials (create the file if it doesn't exist first)
143
- creds_path = Path(cli.paths.host_aws_credentials_file)
144
- creds_path.touch(exist_ok=True)
192
+ paths.aws_credentials_file.touch(exist_ok=True)
145
193
  credentials_updater = ConfigUpdater()
146
- credentials_updater.read(cli.paths.host_aws_credentials_file)
194
+ credentials_updater.read(paths.aws_credentials_file)
147
195
 
148
196
  update_config_section(
149
197
  credentials_updater,