imbi-plugin-github 2.21.0__tar.gz → 2.21.1__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.
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/PKG-INFO +2 -2
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/pyproject.toml +2 -2
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/deployment.py +52 -13
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/tests/test_deployment.py +60 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/.gitignore +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/README.md +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/moon.yml +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/README.md +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/__init__.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/_app_auth.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/_hosts.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/_repos.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/commits.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/doctor.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/identity.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/lifecycle.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/plugin.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/pull_requests.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/py.typed +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/tests/__init__.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/tests/test_commits.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/tests/test_doctor.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/tests/test_hosts.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/tests/test_identity.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/tests/test_lifecycle.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/tests/test_pull_requests.py +0 -0
- {imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/tests/test_repos.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: imbi-plugin-github
|
|
3
|
-
Version: 2.21.
|
|
3
|
+
Version: 2.21.1
|
|
4
4
|
Summary: GitHub identity plugin for Imbi (github.com / GHEC / GHES)
|
|
5
5
|
Author-email: "Gavin M. Roy" <gavinr@aweber.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
13
13
|
Requires-Python: >=3.14
|
|
14
14
|
Requires-Dist: httpx>=0.27
|
|
15
|
-
Requires-Dist: imbi-common[databases]==2.21.
|
|
15
|
+
Requires-Dist: imbi-common[databases]==2.21.1
|
|
16
16
|
Requires-Dist: pydantic>=2
|
|
17
17
|
Requires-Dist: pyjwt[crypto]>=2.8
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "imbi-plugin-github"
|
|
3
|
-
version = "2.21.
|
|
3
|
+
version = "2.21.1"
|
|
4
4
|
description = "GitHub identity plugin for Imbi (github.com / GHEC / GHES)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -18,7 +18,7 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
dependencies = [
|
|
20
20
|
"httpx>=0.27",
|
|
21
|
-
"imbi-common[databases]==2.21.
|
|
21
|
+
"imbi-common[databases]==2.21.1",
|
|
22
22
|
"pydantic>=2",
|
|
23
23
|
"pyjwt[crypto]>=2.8",
|
|
24
24
|
]
|
{imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/deployment.py
RENAMED
|
@@ -46,6 +46,7 @@ from imbi.common.plugins.base import (
|
|
|
46
46
|
RefInfo,
|
|
47
47
|
ReleaseInfo,
|
|
48
48
|
RemoteDeployment,
|
|
49
|
+
RemoteRelease,
|
|
49
50
|
WorkflowFile,
|
|
50
51
|
)
|
|
51
52
|
from imbi.common.plugins.errors import PluginAuthenticationFailed
|
|
@@ -1196,19 +1197,19 @@ class GitHubDeployment(DeploymentCapability):
|
|
|
1196
1197
|
run_cache[run_id] = result
|
|
1197
1198
|
return result
|
|
1198
1199
|
|
|
1199
|
-
async def
|
|
1200
|
+
async def _release_for_ref(
|
|
1200
1201
|
self, client: httpx.AsyncClient, ref: str
|
|
1201
|
-
) ->
|
|
1202
|
-
"""Return the GitHub release
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1202
|
+
) -> RemoteRelease | None:
|
|
1203
|
+
"""Return the GitHub release for ``ref``, metadata included.
|
|
1204
|
+
|
|
1205
|
+
The single release lookup every caller degrades through: a 404 /
|
|
1206
|
+
non-200 / parse failure yields ``None``, and a ``403`` (token lacks
|
|
1207
|
+
scope to read releases) is cached process-wide so a forbidden token
|
|
1208
|
+
short-circuits instead of re-issuing the request for every
|
|
1209
|
+
deployment on every resync sweep. ``author`` is the login GitHub
|
|
1210
|
+
credits with the release and ``author_subject`` its numeric user id,
|
|
1211
|
+
which the host resolves to an Imbi user through the identity plugins
|
|
1212
|
+
on the same service.
|
|
1212
1213
|
"""
|
|
1213
1214
|
if _releases_forbidden(client):
|
|
1214
1215
|
return None
|
|
@@ -1227,8 +1228,46 @@ class GitHubDeployment(DeploymentCapability):
|
|
|
1227
1228
|
data = typing.cast(dict[str, typing.Any], resp.json())
|
|
1228
1229
|
except ValueError:
|
|
1229
1230
|
return None
|
|
1231
|
+
author: dict[str, typing.Any] = data.get('author') or {}
|
|
1232
|
+
login = author.get('login')
|
|
1233
|
+
subject = author.get('id')
|
|
1230
1234
|
body = data.get('body')
|
|
1231
|
-
|
|
1235
|
+
name = data.get('name')
|
|
1236
|
+
html_url = data.get('html_url')
|
|
1237
|
+
return RemoteRelease(
|
|
1238
|
+
tag=ref,
|
|
1239
|
+
name=str(name) if name else None,
|
|
1240
|
+
body_markdown=str(body) if body else None,
|
|
1241
|
+
author=str(login) if login else None,
|
|
1242
|
+
author_subject=str(subject) if subject is not None else None,
|
|
1243
|
+
html_url=str(html_url) if html_url else None,
|
|
1244
|
+
published_at=_parse_iso(data.get('published_at')),
|
|
1245
|
+
)
|
|
1246
|
+
|
|
1247
|
+
async def get_release(
|
|
1248
|
+
self,
|
|
1249
|
+
ctx: PluginContext,
|
|
1250
|
+
credentials: dict[str, str],
|
|
1251
|
+
tag: str,
|
|
1252
|
+
) -> RemoteRelease | None:
|
|
1253
|
+
"""Return the GitHub release for ``tag`` with its author."""
|
|
1254
|
+
async with self._client(ctx, credentials) as client:
|
|
1255
|
+
return await self._release_for_ref(client, tag)
|
|
1256
|
+
|
|
1257
|
+
async def _release_notes_for_ref(
|
|
1258
|
+
self, client: httpx.AsyncClient, ref: str
|
|
1259
|
+
) -> str | None:
|
|
1260
|
+
"""Return the GitHub release notes body for a deployed ref.
|
|
1261
|
+
|
|
1262
|
+
A deployment created against a semver tag has a matching GitHub
|
|
1263
|
+
release whose ``body`` is the "What's Changed" markdown the host
|
|
1264
|
+
persists on the ``Release`` node. Refs that aren't a release tag
|
|
1265
|
+
(branches, raw SHAs) 404 in :meth:`_release_for_ref` and yield
|
|
1266
|
+
``None`` -- resync is never blocked by a missing or unreadable
|
|
1267
|
+
release.
|
|
1268
|
+
"""
|
|
1269
|
+
release = await self._release_for_ref(client, ref)
|
|
1270
|
+
return release.body_markdown if release else None
|
|
1232
1271
|
|
|
1233
1272
|
async def _latest_status(
|
|
1234
1273
|
self, client: httpx.AsyncClient, deployment_id: str
|
|
@@ -1555,6 +1555,66 @@ class GetReleaseNotesTestCase(unittest.IsolatedAsyncioTestCase):
|
|
|
1555
1555
|
notes = await plugin.get_release_notes(_ctx(), _CREDS, '9.9.9')
|
|
1556
1556
|
self.assertIsNone(notes)
|
|
1557
1557
|
|
|
1558
|
+
@respx.mock
|
|
1559
|
+
async def test_get_release_carries_the_remote_author(self) -> None:
|
|
1560
|
+
# Attribution comes from GitHub: the login it credits with the
|
|
1561
|
+
# release, plus the numeric id the host resolves to an Imbi user.
|
|
1562
|
+
respx.get(
|
|
1563
|
+
'https://api.github.com/repos/octo/demo/releases/tags/3.23.4'
|
|
1564
|
+
).mock(
|
|
1565
|
+
return_value=httpx.Response(
|
|
1566
|
+
200,
|
|
1567
|
+
json={
|
|
1568
|
+
'tag_name': '3.23.4',
|
|
1569
|
+
'name': 'Release 3.23.4',
|
|
1570
|
+
'body': '## Fixed\n- the thing',
|
|
1571
|
+
'html_url': 'https://github.com/octo/demo/releases/3.23.4',
|
|
1572
|
+
'published_at': '2026-07-29T12:00:00Z',
|
|
1573
|
+
'author': {'login': 'octocat', 'id': 583231},
|
|
1574
|
+
},
|
|
1575
|
+
)
|
|
1576
|
+
)
|
|
1577
|
+
plugin = GitHubDeployment()
|
|
1578
|
+
release = await plugin.get_release(_ctx(), _CREDS, '3.23.4')
|
|
1579
|
+
assert release is not None
|
|
1580
|
+
self.assertEqual('octocat', release.author)
|
|
1581
|
+
self.assertEqual('583231', release.author_subject)
|
|
1582
|
+
self.assertEqual('Release 3.23.4', release.name)
|
|
1583
|
+
self.assertEqual('## Fixed\n- the thing', release.body_markdown)
|
|
1584
|
+
self.assertEqual(
|
|
1585
|
+
'https://github.com/octo/demo/releases/3.23.4', release.html_url
|
|
1586
|
+
)
|
|
1587
|
+
self.assertEqual(
|
|
1588
|
+
datetime.datetime(2026, 7, 29, 12, 0, tzinfo=datetime.UTC),
|
|
1589
|
+
release.published_at,
|
|
1590
|
+
)
|
|
1591
|
+
|
|
1592
|
+
@respx.mock
|
|
1593
|
+
async def test_get_release_returns_none_when_no_release(self) -> None:
|
|
1594
|
+
respx.get(
|
|
1595
|
+
'https://api.github.com/repos/octo/demo/releases/tags/9.9.9'
|
|
1596
|
+
).mock(return_value=httpx.Response(404, json={'message': 'Not Found'}))
|
|
1597
|
+
plugin = GitHubDeployment()
|
|
1598
|
+
self.assertIsNone(await plugin.get_release(_ctx(), _CREDS, '9.9.9'))
|
|
1599
|
+
|
|
1600
|
+
@respx.mock
|
|
1601
|
+
async def test_get_release_tolerates_a_missing_author(self) -> None:
|
|
1602
|
+
# A release with no author (a tag-only release, or a deleted
|
|
1603
|
+
# account) still yields its body rather than failing the lookup.
|
|
1604
|
+
respx.get(
|
|
1605
|
+
'https://api.github.com/repos/octo/demo/releases/tags/1.0.0'
|
|
1606
|
+
).mock(
|
|
1607
|
+
return_value=httpx.Response(
|
|
1608
|
+
200, json={'tag_name': '1.0.0', 'body': 'notes'}
|
|
1609
|
+
)
|
|
1610
|
+
)
|
|
1611
|
+
plugin = GitHubDeployment()
|
|
1612
|
+
release = await plugin.get_release(_ctx(), _CREDS, '1.0.0')
|
|
1613
|
+
assert release is not None
|
|
1614
|
+
self.assertIsNone(release.author)
|
|
1615
|
+
self.assertIsNone(release.author_subject)
|
|
1616
|
+
self.assertEqual('notes', release.body_markdown)
|
|
1617
|
+
|
|
1558
1618
|
|
|
1559
1619
|
class CheckStatusTestCase(unittest.IsolatedAsyncioTestCase):
|
|
1560
1620
|
@respx.mock
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/_app_auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
{imbi_plugin_github-2.21.0 → imbi_plugin_github-2.21.1}/src/imbi/plugins/github/pull_requests.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|