Skip to content

Conversation

@borland667
Copy link
Contributor

@borland667 borland667 commented Oct 24, 2025

Add Automatic Credential Refresh for AWS SSO

What?

New Features

  • Automatic refresh: --refresh-all flag on leverage aws sso login to refresh all account credentials after SSO login
  • Manual refresh: New leverage aws sso refresh [--force] command
  • Smart renewal: Skips valid credentials (>30 min remaining) unless --force specified
  • Error handling: Continues on permission errors, provides success/failure summary

Infrastructure Fixes

  • Fixed Click option parsing in _handle_subcommand() for nested commands
  • Fixed --help and help syntaxes for all wrapped Python commands
  • Fixed args propagation using inspect.signature()

Tests

  • 8 new unit tests (94% coverage) + 1 integration test with real file I/O
  • All edge cases covered: success, errors, force refresh, missing profiles

Why?

Currently, after SSO login, you need to manually refresh credentials for each AWS account. Often this means running terraform plan or terraform output just to trigger credential refresh - even when you only want to generate a kubeconfig for EKS or run a simple AWS CLI command. This is time-consuming and annoying.

This PR streamlines the workflow with automatic or one-command refresh that skips accounts with valid credentials.

Benefits: Time savings, better UX, no unnecessary Terraform operations, graceful error handling, backward compatible (opt-in)

Usage

# Auto-refresh on login
leverage aws sso login --refresh-all

# Manual refresh (smart - skips valid credentials)
leverage aws sso refresh

# Force refresh all
leverage aws sso refresh --force

# Help works both ways now
leverage aws sso refresh --help
leverage aws sso refresh help

Testing

leverage aws sso login --refresh-all    # Test auto-refresh
leverage aws sso refresh                # Test smart refresh
leverage aws sso refresh --force        # Test force refresh
leverage aws sso refresh --help         # Verify help works

Before Release

Review the checklist here

Summary by CodeRabbit

  • New Features

    • Added --refresh-all flag to SSO login command to refresh credentials for all configured accounts in bulk
    • Introduced new sso refresh command for manual credential refresh
    • Added --force option to refresh credentials regardless of expiration status
  • Improvements

    • Enhanced command-line help behavior for better user experience

Add refresh_all_accounts_credentials() function to refresh credentials
for all configured AWS accounts automatically. This feature provides both
automatic refresh on SSO login and manual refresh capabilities.

New Features:
- Add --refresh-all flag to 'leverage aws sso login' command to
  automatically refresh all account credentials after login
- Add 'leverage aws sso refresh [--force]' standalone command for
  manual credential refresh
- Implement smart credential renewal that skips accounts with valid
  credentials unless --force is specified
- Add comprehensive progress logging for each account operation
- Handle permission errors gracefully and continue with other accounts

Infrastructure Improvements:
- Fix _handle_subcommand to properly handle Click options in nested
  command groups using make_context() for proper option parsing
- Add explicit help handling for both --help and help syntaxes
- Fix args propagation to nested command groups using inspect.signature
- Fix ValueError when caller_name not in args tuple
- Improve error handling with proper ExitError usage

Testing:
- Add 8 comprehensive unit tests covering success, force refresh,
  no profiles, permission errors, and token errors scenarios
- Add integration test with real file operations using tmp_path
- Enhance all test docstrings with clear intent, scenarios, and
  technical details
- Minimize mocking to only external dependencies (boto3, time)
- All 146 tests passing with zero regressions

Production tested with 5 AWS accounts - all scenarios working correctly.
Maintains full backward compatibility with existing functionality.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 24, 2025

Walkthrough

This PR adds bulk credential refresh capability for AWS SSO accounts. A new refresh_all_accounts_credentials() function refreshes credentials across all configured accounts with conditional expiration checks. The CLI is extended with a --refresh-all flag for the login command and a new standalone refresh subcommand. Utility functions are enhanced to improve subcommand resolution and help display logic. Comprehensive tests validate the new functionality across multiple scenarios.

Changes

Cohort / File(s) Summary
Core credential refresh logic
leverage/modules/auth.py
Added refresh_all_accounts_credentials(cli, force_refresh=False) function to bulk-refresh credentials for all configured SSO accounts, with support for conditional expiration checks, error handling, and per-profile logging.
CLI integration
leverage/modules/aws.py
Extended login() signature to accept refresh_all parameter and wire it to trigger bulk refresh. Introduced new refresh(cli, force) subcommand with --force flag. Added --refresh-all option to login command and imported required dependencies.
Utility enhancements
leverage/modules/utils.py
Enhanced subcommand resolution with robust fallback for caller_name lookup, nuanced help handling logic, and conditional invocation path that distinguishes between group and leaf commands, including context-scoped invocation for complex command parsing.
Test coverage
tests/test_modules/test_auth.py
Added comprehensive test suite for refresh_all_accounts_credentials() covering multi-account scenarios, force-refresh flows, permission/token error handling, and file I/O validation with extensive mocking and logging assertions.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as CLI (aws.py)
    participant Auth as Auth Module
    participant AWS as AWS SSO/STS
    participant FS as Filesystem
    
    User->>CLI: sso login --refresh-all
    CLI->>Auth: refresh_all_accounts_credentials(force_refresh=False)
    
    rect rgba(100, 150, 200, 0.2)
    note over Auth: For each configured account
    Auth->>AWS: Get SSO access token
    Auth->>AWS: Get role credentials via STS
    end
    
    alt Success
        Auth->>FS: Update AWS config (expiration)
        Auth->>FS: Write credentials
        Auth->>CLI: Log per-profile success
    else Permission Error
        Auth->>CLI: Log and skip account
    end
    
    Auth->>CLI: Return with summary
    CLI->>User: Display results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

The changes span three functional areas with heterogeneous logic: new credential refresh logic with error handling in auth.py, CLI wiring and parameter propagation in aws.py, and intricate subcommand resolution with conditional branching in utils.py. While auth.py and test coverage follow familiar patterns, utils.py introduces nuanced conditional logic for help display and command invocation that requires careful review to ensure correct behavior across different command types and contexts.

Poem

🐰 Hop hop, credentials refreshed with glee!
Bulk accounts sync'd across the SSO sea,
New flags and commands make logins flow,
Help displays smart, with utils aglow!
All tests pass—the refresh dance is complete!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Add automatic credential refresh for AWS SSO" accurately captures the primary objective of this changeset. The PR introduces a new refresh_all_accounts_credentials() function in auth.py, adds a --refresh-all flag to the sso login command in aws.py, and creates a new sso refresh command, all designed to enable credential refresh for AWS SSO accounts. While the PR also includes infrastructure fixes for Click option parsing and argument propagation in utils.py, these are secondary supporting changes that enable the main feature. The title is concise, clear, and specific enough that a teammate scanning the history would understand the core change involves adding credential refresh capabilities for AWS SSO.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
leverage/modules/utils.py (1)

31-33: Exit code check is inverted; raises on success.

Currently raises Exit when exit_code is 0 (success). Flip the condition.

Apply this diff:

-        if not exit_code:
-            raise Exit(exit_code)
+        if exit_code:
+            raise Exit(exit_code)
🧹 Nitpick comments (7)
leverage/modules/utils.py (2)

39-51: Show help with a subcommand context.

Use make_context for accurate command path/options; return after printing help.

Apply this diff:

-        if is_help_request and (not is_group or len(remaining_args) == 1):
-            # Show help for the subcommand
-            click.echo(subcommand_obj.get_help(context))
+        if is_help_request and (not is_group or len(remaining_args) == 1):
+            # Show help for the subcommand with its own context
+            with subcommand_obj.make_context(subcommand, [], parent=context) as sub_ctx:
+                click.echo(subcommand_obj.get_help(sub_ctx))
+            return

55-63: Guard callback None and avoid TypeError with inspect.signature.

Some Click groups may have no callback; handle that path and prefer direct pass-through for groups/args-style callbacks.

Apply this diff:

-            sig = inspect.signature(subcommand_obj.callback)
-            if "args" in sig.parameters:
-                # Pass the remaining args to the subcommand (for groups)
-                context.invoke(subcommand_obj, args=remaining_args)
-            else:
-                # Create a new context with remaining args so Click can parse options
-                with subcommand_obj.make_context(subcommand, list(remaining_args), parent=context) as sub_ctx:
-                    with context.scope(cleanup=False):
-                        subcommand_obj.invoke(sub_ctx)
+            callback = getattr(subcommand_obj, "callback", None)
+            if callback is None:
+                # Group without a callback; pass args directly.
+                context.invoke(subcommand_obj, args=remaining_args)
+                return
+            sig = inspect.signature(callback)
+            if "args" in sig.parameters or is_group:
+                # Pass the remaining args directly (common for groups).
+                context.invoke(subcommand_obj, args=remaining_args)
+            else:
+                # Create a new context so Click can parse options.
+                with subcommand_obj.make_context(subcommand, list(remaining_args), parent=context) as sub_ctx:
+                    with context.scope(cleanup=False):
+                        subcommand_obj.invoke(sub_ctx)
leverage/modules/auth.py (3)

191-196: Preserve original cause when wrapping token errors.

Re-raise with context to aid debugging; optional to narrow exception types.

Apply this diff:

-    try:
-        access_token = cli.get_sso_access_token()
-    except Exception as e:
-        raise ExitError(1, f"Failed to get SSO access token: {e}")
+    try:
+        access_token = cli.get_sso_access_token()
+    except Exception as e:
+        raise ExitError(1, f"Failed to get SSO access token: {e}") from e

Optionally narrow to specific exceptions (e.g., FileNotFoundError, KeyError, json.JSONDecodeError).


239-247: Remove unnecessary else after continue.

Slight simplification and aligns with lints.

Apply this diff:

-            except ClientError as error:
-                if error.response["Error"]["Code"] in ("AccessDeniedException", "ForbiddenException"):
-                    logger.warning(
-                        f"No permission to assume role [bold]{sso_role}[/bold] in {account_name} account. Skipping."
-                    )
-                    error_count += 1
-                    continue
-                else:
-                    raise
+            except ClientError as error:
+                if error.response["Error"]["Code"] in ("AccessDeniedException", "ForbiddenException"):
+                    logger.warning(
+                        f"No permission to assume role [bold]{sso_role}[/bold] in {account_name} account. Skipping."
+                    )
+                    error_count += 1
+                    continue
+                raise

177-186: Defensive handling of ConfigUpdater.sections() return type.

If sections() yields Section objects, use .name for matching.

Apply this diff:

-    sso_profiles = []
-    for section in config_updater.sections():
-        if section.startswith(f"profile {cli.project}-sso-") and section != f"profile {cli.project}-sso":
-            sso_profiles.append(section)
+    sso_profiles = []
+    for section in config_updater.sections():
+        section_name = section if isinstance(section, str) else getattr(section, "name", str(section))
+        if section_name.startswith(f"profile {cli.project}-sso-") and section_name != f"profile {cli.project}-sso":
+            sso_profiles.append(section_name)
tests/test_modules/test_auth.py (2)

341-347: Remove unused helper or silence ARG001.

read_text_side_effect_multi is unused; delete it or prefix with underscore to silence Ruff.

Apply this diff to remove:

-def read_text_side_effect_multi(self: PosixPath, *args, **kwargs):
-    """
-    Side effect for reading multi-account config files.
-    """
-    return data_dict_multi.get(str(self), data_dict_multi.get(self.name, ""))

374-407: Prefix unused patched args to satisfy Ruff (ARG001).

Several tests receive mocks they don't use (mock_open, mock_boto, mock_update_conf). Prefix with underscores or add noqa to signatures.

Example:

-def test_refresh_all_accounts_credentials_success(mock_open, mock_boto, mock_update_conf, sso_container, caplog):
+def test_refresh_all_accounts_credentials_success(_mock_open, _mock_boto, _mock_update_conf, sso_container, caplog):

Repeat similarly for other tests flagged by ARG001.

Also applies to: 412-437, 461-480, 486-535, 539-561, 568-597

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf1684b and 81739de.

📒 Files selected for processing (4)
  • leverage/modules/auth.py (1 hunks)
  • leverage/modules/aws.py (4 hunks)
  • leverage/modules/utils.py (3 hunks)
  • tests/test_modules/test_auth.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
leverage/modules/auth.py (4)
leverage/logger.py (4)
  • info (116-118)
  • warning (122-124)
  • debug (110-112)
  • error (128-130)
leverage/path.py (2)
  • host_aws_profiles_file (203-204)
  • host_aws_credentials_file (207-208)
leverage/container.py (2)
  • sso_region_from_main_profile (327-333)
  • get_sso_access_token (322-324)
leverage/_utils.py (1)
  • ExitError (109-116)
tests/test_modules/test_auth.py (4)
leverage/modules/auth.py (4)
  • refresh_layer_credentials (83-157)
  • get_layer_profile (17-46)
  • SkipProfile (13-14)
  • refresh_all_accounts_credentials (160-275)
leverage/container.py (1)
  • get_sso_access_token (322-324)
leverage/_utils.py (1)
  • ExitError (109-116)
leverage/path.py (2)
  • host_aws_profiles_file (203-204)
  • host_aws_credentials_file (207-208)
leverage/modules/aws.py (3)
leverage/_utils.py (2)
  • get_or_create_section (181-185)
  • ExitError (109-116)
leverage/modules/auth.py (1)
  • refresh_all_accounts_credentials (160-275)
leverage/_internals.py (1)
  • pass_container (44-53)
leverage/modules/utils.py (1)
tests/conftest.py (1)
  • context (29-34)
🪛 Pylint (4.0.1)
leverage/modules/auth.py

[refactor] 160-160: Too many local variables (21/15)

(R0914)


[refactor] 239-246: Unnecessary "else" after "continue", remove the "else" and de-indent the code inside it

(R1724)


[refactor] 160-160: Too many statements (54/50)

(R0915)

leverage/modules/aws.py

[error] 225-225: Instance of 'ExitError' has no 'code' member

(E1101)


[error] 255-255: Instance of 'ExitError' has no 'code' member

(E1101)

leverage/modules/utils.py

[error] 1-1: Unrecognized option found: suggestion-mode

(E0015)


[refactor] 1-1: Useless option value for '--disable', 'print-statement' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'parameter-unpacking' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'unpacking-in-except' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'old-raise-syntax' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'backtick' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'import-star-module-level' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'apply-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'basestring-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'buffer-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'cmp-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'coerce-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'execfile-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'file-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'long-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'raw_input-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'reduce-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'standarderror-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'unicode-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'xrange-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'coerce-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'delslice-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'getslice-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'setslice-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'no-absolute-import' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'old-division' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'dict-iter-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'dict-view-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'next-method-called' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'metaclass-assignment' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'indexing-exception' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'raising-string' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'reload-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'oct-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'hex-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'nonzero-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'cmp-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'input-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'round-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'intern-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'unichr-builtin' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'map-builtin-not-iterating' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'zip-builtin-not-iterating' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'range-builtin-not-iterating' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'filter-builtin-not-iterating' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'using-cmp-argument' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'div-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'idiv-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'rdiv-method' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'exception-message-attribute' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'invalid-str-codec' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'sys-max-int' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'bad-python3-import' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'deprecated-string-function' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'deprecated-str-translate-call' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'deprecated-itertools-function' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'deprecated-types-field' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'next-method-defined' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'dict-items-not-iterating' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'dict-keys-not-iterating' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'dict-values-not-iterating' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'deprecated-operator-function' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'deprecated-urllib-function' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'xreadlines-attribute' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'deprecated-sys-function' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'exception-escape' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)


[refactor] 1-1: Useless option value for '--disable', 'comprehension-escape' was removed from pylint, see pylint-dev/pylint#4942.

(R0022)

🪛 Ruff (0.14.1)
leverage/modules/auth.py

194-194: Do not catch blind exception: Exception

(BLE001)


195-195: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


271-271: Do not catch blind exception: Exception

(BLE001)

tests/test_modules/test_auth.py

341-341: Unused function argument: args

(ARG001)


341-341: Unused function argument: kwargs

(ARG001)


348-348: Unused function argument: args

(ARG001)


348-348: Unused function argument: kwargs

(ARG001)


374-374: Unused function argument: mock_open

(ARG001)


374-374: Unused function argument: mock_boto

(ARG001)


374-374: Unused function argument: mock_update_conf

(ARG001)


412-412: Unused function argument: mock_open

(ARG001)


412-412: Unused function argument: mock_boto

(ARG001)


450-450: Unused function argument: args

(ARG001)


450-450: Unused function argument: kwargs

(ARG001)


461-461: Unused function argument: mock_open

(ARG001)


461-461: Unused function argument: mock_boto

(ARG001)


486-486: Unused function argument: mock_open

(ARG001)


486-486: Unused function argument: mock_update_conf

(ARG001)


539-539: Unused function argument: mock_open

(ARG001)


568-568: Unused function argument: mock_open

(ARG001)


568-568: Unused function argument: mock_boto

(ARG001)

leverage/modules/aws.py

225-225: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


255-255: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (1)
leverage/modules/utils.py (1)

19-23: Solid fallback when caller_name is absent.

Graceful recovery for --help and similar flows looks good.

Comment on lines +221 to +226
if refresh_all:
try:
refresh_all_accounts_credentials(cli, force_refresh=False)
except ExitError as e:
raise Exit(e.code)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Propagate ExitError correctly; use exit_code and keep cause.

ExitError inherits click.Exit; use e.exit_code and raise from e.

Apply this diff:

-    if refresh_all:
-        try:
-            refresh_all_accounts_credentials(cli, force_refresh=False)
-        except ExitError as e:
-            raise Exit(e.code)
+    if refresh_all:
+        try:
+            refresh_all_accounts_credentials(cli, force_refresh=False)
+        except ExitError as e:
+            raise Exit(e.exit_code) from e
🧰 Tools
🪛 Pylint (4.0.1)

[error] 225-225: Instance of 'ExitError' has no 'code' member

(E1101)

🪛 Ruff (0.14.1)

225-225: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
In leverage/modules/aws.py around lines 221 to 226, the except block catches
ExitError (which inherits click.Exit) but raises Exit(e.code) losing the
original exit code and exception cause; update the handler to raise
Exit(e.exit_code) from e so the correct exit code is propagated and the original
exception is preserved as the cause.

Comment on lines +249 to +255
@pass_container
def refresh(cli, force):
"""Refresh credentials for all configured accounts"""
try:
refresh_all_accounts_credentials(cli, force_refresh=force)
except ExitError as e:
raise Exit(e.code)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same fix for refresh subcommand.

Use e.exit_code and raise from e.

Apply this diff:

-    try:
-        refresh_all_accounts_credentials(cli, force_refresh=force)
-    except ExitError as e:
-        raise Exit(e.code)
+    try:
+        refresh_all_accounts_credentials(cli, force_refresh=force)
+    except ExitError as e:
+        raise Exit(e.exit_code) from e
🧰 Tools
🪛 Pylint (4.0.1)

[error] 255-255: Instance of 'ExitError' has no 'code' member

(E1101)

🪛 Ruff (0.14.1)

255-255: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
In leverage/modules/aws.py around lines 249 to 255, the except block for
ExitError should use the exception's exit_code attribute and preserve the
original exception context; change the handler to raise Exit(e.exit_code) from e
so the ExitError's exit_code is used and the original exception is chained.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant