Skip to content

Commit 653af66

Browse files
committed
Fix scoped installation-token mint that always failed at runtime
**Impact:** pytorch-advisor-coverage lambda (token minting for workflow_dispatch) **Risk:** low ## What Mint the scoped installation token via `GithubIntegration.get_access_token` instead of reading `.token` off a standalone `Auth.AppInstallationAuth`, and wrap the call so mint failures name the App and installation ids. ## Why On PyGithub 2.6.1 `Auth.AppInstallationAuth` only builds its internal integration inside `withRequester`, which never runs until the auth is handed to a `github.Github(...)`. Reading `.token` off the standalone instance therefore always asserts, so the old code path could never actually mint a token at runtime. The previous test passed only because the whole `github` module was mocked, hiding the bug. # Notes The test now patches only `GithubIntegration`, exercising the real PyGithub call path so a mint that can't produce a token fails in CI rather than silently passing. A second test covers the wrapped error message, which surfaces the App/installation ids to distinguish a wrong-App key ("A JSON web token could not be decoded") from a genuine outage. Token scoping (`actions:write` only) is unchanged. Signed-off-by: Jean Schmidt <contato@jschmidt.me>
1 parent 5232da6 commit 653af66

2 files changed

Lines changed: 49 additions & 16 deletions

File tree

aws/lambda/pytorch-advisor-coverage/advisor_coverage/bootstrap.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,29 @@ def _get_secret_from_aws(secret_store_name: str) -> _AWSSecrets:
9494
def _mint_scoped_installation_token(app_id: str, pem: str, installation_id: int) -> str:
9595
"""Mint an installation token scoped to `actions:write` only.
9696
97-
Without token_permissions the mint inherits the App's full permission set
97+
Without explicit permissions the mint inherits the App's full permission set
9898
(incl. contents:write → revert-capable). Scoping to actions:write is the
9999
minimum for workflow_dispatch and removes revert capability entirely.
100+
101+
Minted through GithubIntegration rather than Auth.AppInstallationAuth:
102+
PyGithub (2.6.1) only builds that auth object's internal integration inside
103+
`withRequester`, which nothing calls until the auth is handed to a
104+
`github.Github(...)`, so reading `.token` off a standalone instance always
105+
asserts.
100106
"""
101-
app_auth = github.Auth.AppAuth(app_id, pem)
102-
inst_auth = github.Auth.AppInstallationAuth(
103-
app_auth,
104-
installation_id=installation_id,
105-
token_permissions=_DISPATCH_TOKEN_PERMISSIONS,
106-
)
107-
return inst_auth.token
107+
integration = github.GithubIntegration(auth=github.Auth.AppAuth(app_id, pem))
108+
try:
109+
return integration.get_access_token(
110+
installation_id, permissions=_DISPATCH_TOKEN_PERMISSIONS
111+
).token
112+
except github.GithubException as e:
113+
# GitHub answers a key that is validly formed but registered to a
114+
# different App with "A JSON web token could not be decoded" — naming the
115+
# identifiers is what distinguishes that from a genuine outage.
116+
raise RuntimeError(
117+
f"Failed to mint an installation token for GITHUB_APP_ID={app_id}, "
118+
f"GITHUB_INSTALLATION_ID={installation_id}: {e}"
119+
) from e
108120

109121

110122
def setup_clients(config: CoverageConfig) -> None:

aws/lambda/pytorch-advisor-coverage/advisor_coverage/tests/test_advisor_coverage.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -835,18 +835,39 @@ def test_configure_logging_pins_secret_loggers(self):
835835
for name in ("github", "github.Requester", "botocore", "boto3", "urllib3"):
836836
self.assertEqual(logging.getLogger(name).level, logging.WARNING, name)
837837

838-
@patch("advisor_coverage.bootstrap.github")
839-
def test_mint_scopes_token_to_actions_write(self, mock_github):
838+
# Patches only GithubIntegration, so the surrounding call path runs against
839+
# real PyGithub: a mint that cannot produce a token without a Requester
840+
# fails here instead of passing against an all-mocked github module.
841+
@patch("advisor_coverage.bootstrap.github.GithubIntegration")
842+
def test_mint_scopes_token_to_actions_write(self, mock_integration):
843+
import github
844+
840845
from advisor_coverage.bootstrap import _mint_scoped_installation_token
841846

842-
inst = MagicMock()
843-
inst.token = "ghs_scoped"
844-
mock_github.Auth.AppInstallationAuth.return_value = inst
847+
get_token = mock_integration.return_value.get_access_token
848+
get_token.return_value.token = "ghs_scoped"
845849
token = _mint_scoped_installation_token("app-id", "PEM", 4242)
846850
self.assertEqual(token, "ghs_scoped")
847-
kwargs = mock_github.Auth.AppInstallationAuth.call_args.kwargs
848-
self.assertEqual(kwargs["token_permissions"], {"actions": "write"})
849-
self.assertEqual(kwargs["installation_id"], 4242)
851+
self.assertIsInstance(
852+
mock_integration.call_args.kwargs["auth"], github.Auth.AppAuth
853+
)
854+
args, kwargs = get_token.call_args
855+
self.assertEqual(args, (4242,))
856+
self.assertEqual(kwargs["permissions"], {"actions": "write"})
857+
858+
@patch("advisor_coverage.bootstrap.github.GithubIntegration")
859+
def test_mint_failure_names_the_identifiers(self, mock_integration):
860+
import github
861+
862+
from advisor_coverage.bootstrap import _mint_scoped_installation_token
863+
864+
mock_integration.return_value.get_access_token.side_effect = (
865+
github.GithubException(401, {"message": "could not be decoded"}, None)
866+
)
867+
with self.assertRaises(RuntimeError) as ctx:
868+
_mint_scoped_installation_token("app-id", "PEM", 4242)
869+
self.assertIn("app-id", str(ctx.exception))
870+
self.assertIn("4242", str(ctx.exception))
850871

851872
def test_setup_clients_uses_scoped_token(self):
852873
cfg = make_config(

0 commit comments

Comments
 (0)