imbi-plugin-github 2.41.0__tar.gz → 2.43.0__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.41.0 → imbi_plugin_github-2.43.0}/PKG-INFO +2 -2
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/pyproject.toml +2 -2
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/deployment.py +5 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/plugin.py +10 -0
- imbi_plugin_github-2.43.0/src/imbi/plugins/github/public.py +84 -0
- imbi_plugin_github-2.43.0/src/imbi/plugins/github/secret_provider.py +734 -0
- imbi_plugin_github-2.43.0/tests/test_public.py +77 -0
- imbi_plugin_github-2.43.0/tests/test_secret_provider.py +603 -0
- imbi_plugin_github-2.43.0/tests/test_secret_provider_live.py +160 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/.gitignore +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/README.md +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/moon.yml +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/README.md +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/__init__.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/_app_auth.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/_hosts.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/_repos.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/commits.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/doctor.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/identity.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/lifecycle.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/pull_requests.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/py.typed +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/__init__.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/test_app_auth_scopes.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/test_commits.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/test_deployment.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/test_doctor.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/test_hosts.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/test_identity.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/test_lifecycle.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/test_pull_requests.py +0 -0
- {imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/tests/test_repos.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: imbi-plugin-github
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.43.0
|
|
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.
|
|
15
|
+
Requires-Dist: imbi-common[databases]==2.43.0
|
|
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.
|
|
3
|
+
version = "2.43.0"
|
|
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
|
+
"imbi-common[databases]==2.43.0",
|
|
22
22
|
"pydantic>=2",
|
|
23
23
|
"pyjwt[crypto]>=2.8",
|
|
24
24
|
]
|
{imbi_plugin_github-2.41.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/deployment.py
RENAMED
|
@@ -65,6 +65,7 @@ from imbi.common.plugins.base import (
|
|
|
65
65
|
WorkflowFile,
|
|
66
66
|
)
|
|
67
67
|
from imbi.common.plugins.errors import PluginAuthenticationFailed
|
|
68
|
+
from imbi.plugins.github import public
|
|
68
69
|
from imbi.plugins.github._hosts import flavor_host, host_to_api_base
|
|
69
70
|
from imbi.plugins.github._repos import (
|
|
70
71
|
derive_owner_repo_from_links,
|
|
@@ -654,6 +655,10 @@ class GitHubDeployment(DeploymentCapability):
|
|
|
654
655
|
per call.
|
|
655
656
|
"""
|
|
656
657
|
|
|
658
|
+
@classmethod
|
|
659
|
+
def public_reader(cls, url: str) -> public.PublicRepository | None:
|
|
660
|
+
return public.PublicRepository.for_url(url)
|
|
661
|
+
|
|
657
662
|
def _resolve_host(self, ctx: PluginContext) -> str:
|
|
658
663
|
return flavor_host(ctx.integration_options, 'github deployment')
|
|
659
664
|
|
|
@@ -25,6 +25,7 @@ from imbi.common.plugins.base import (
|
|
|
25
25
|
PluginManifest,
|
|
26
26
|
PluginOption,
|
|
27
27
|
WebhookActionsCapability,
|
|
28
|
+
WebhookSignature,
|
|
28
29
|
)
|
|
29
30
|
from imbi.plugins.github.commits import (
|
|
30
31
|
GitHubCommitSync,
|
|
@@ -39,6 +40,7 @@ from imbi.plugins.github.pull_requests import (
|
|
|
39
40
|
GitHubPullRequestSync,
|
|
40
41
|
sync_pull_requests_descriptor,
|
|
41
42
|
)
|
|
43
|
+
from imbi.plugins.github.secret_provider import GitHubApp
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
class GitHubWebhookActions(WebhookActionsCapability):
|
|
@@ -58,6 +60,13 @@ class GitHubWebhookActions(WebhookActionsCapability):
|
|
|
58
60
|
sync_pull_requests_descriptor,
|
|
59
61
|
]
|
|
60
62
|
|
|
63
|
+
@classmethod
|
|
64
|
+
def signature(cls) -> WebhookSignature:
|
|
65
|
+
# The secret of the GitHub webhook is the Webhook.secret.
|
|
66
|
+
return WebhookSignature(
|
|
67
|
+
header='x-hub-signature-256', prefix='sha256=', secret='webhook'
|
|
68
|
+
)
|
|
69
|
+
|
|
61
70
|
|
|
62
71
|
# Integration-level options, asked once per Integration.
|
|
63
72
|
_OPTIONS: list[PluginOption] = [
|
|
@@ -418,4 +427,5 @@ class GitHubPlugin(Plugin):
|
|
|
418
427
|
),
|
|
419
428
|
],
|
|
420
429
|
ops_log_templates=_OPS_LOG_TEMPLATES,
|
|
430
|
+
secret_providers=[GitHubApp],
|
|
421
431
|
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Credential-free reads of a public ``github.com`` repository.
|
|
2
|
+
|
|
3
|
+
:meth:`GitHubDeployment.public_reader
|
|
4
|
+
<imbi.plugins.github.deployment.GitHubDeployment.public_reader>`
|
|
5
|
+
returns a :class:`PublicRepository`. The host uses it when no
|
|
6
|
+
Integration of the organization can read a repository, for example a
|
|
7
|
+
public Claude Code plugin marketplace.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import typing
|
|
12
|
+
import urllib.parse
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from imbi.common.plugins import base
|
|
17
|
+
|
|
18
|
+
_GITHUB_REPO = re.compile(r'[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PublicRepository:
|
|
22
|
+
"""The :class:`~imbi.common.plugins.base.RepositoryReader` of a public
|
|
23
|
+
``github.com`` repository. It ignores ``ctx`` and ``credentials``."""
|
|
24
|
+
|
|
25
|
+
# ponytail: 60 unauthenticated requests an hour per API pod IP; use
|
|
26
|
+
# an org token if many public marketplaces sync often.
|
|
27
|
+
def __init__(self, owner: str, repo: str) -> None:
|
|
28
|
+
self.base = f'https://api.github.com/repos/{owner}/{repo}'
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def for_url(cls, url: str) -> PublicRepository | None:
|
|
32
|
+
"""Return the reader of a ``github.com`` repository URL, or
|
|
33
|
+
``None`` for another host or a path that is not a repository."""
|
|
34
|
+
parts = urllib.parse.urlsplit(url)
|
|
35
|
+
path = parts.path.strip('/').removesuffix('.git')
|
|
36
|
+
if parts.hostname != 'github.com' or not _GITHUB_REPO.fullmatch(path):
|
|
37
|
+
return None
|
|
38
|
+
return cls(*path.split('/'))
|
|
39
|
+
|
|
40
|
+
async def _get(
|
|
41
|
+
self, path: str, accept: str = 'application/vnd.github+json'
|
|
42
|
+
) -> httpx.Response:
|
|
43
|
+
# A renamed or moved repository answers 301 to /repositories/{id}.
|
|
44
|
+
async with httpx.AsyncClient(
|
|
45
|
+
timeout=30, follow_redirects=True
|
|
46
|
+
) as client:
|
|
47
|
+
return await client.get(
|
|
48
|
+
f'{self.base}{path}', headers={'Accept': accept}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
async def resolve_committish(
|
|
52
|
+
self, _ctx: object, _credentials: object, ref: str, /
|
|
53
|
+
) -> base.Ref:
|
|
54
|
+
response = await self._get(f'/commits/{urllib.parse.quote(ref)}')
|
|
55
|
+
response.raise_for_status()
|
|
56
|
+
return base.Ref(name=ref, kind='branch', sha=response.json()['sha'])
|
|
57
|
+
|
|
58
|
+
async def list_refs(
|
|
59
|
+
self,
|
|
60
|
+
_ctx: object,
|
|
61
|
+
_credentials: object,
|
|
62
|
+
/,
|
|
63
|
+
kind: typing.Literal['default', 'branch', 'tag', 'all'] = 'all',
|
|
64
|
+
) -> list[base.Ref]:
|
|
65
|
+
"""Return the default branch only, whatever ``kind`` is."""
|
|
66
|
+
del kind
|
|
67
|
+
response = await self._get('')
|
|
68
|
+
response.raise_for_status()
|
|
69
|
+
branch = str(response.json()['default_branch'])
|
|
70
|
+
ref = await self.resolve_committish(None, None, branch)
|
|
71
|
+
return [ref.model_copy(update={'kind': 'default', 'is_default': True})]
|
|
72
|
+
|
|
73
|
+
async def get_file(
|
|
74
|
+
self, _ctx: object, _credentials: object, path: str, ref: str, /
|
|
75
|
+
) -> str | None:
|
|
76
|
+
response = await self._get(
|
|
77
|
+
f'/contents/{urllib.parse.quote(path)}?'
|
|
78
|
+
f'{urllib.parse.urlencode({"ref": ref})}',
|
|
79
|
+
'application/vnd.github.raw+json',
|
|
80
|
+
)
|
|
81
|
+
if response.status_code == 404:
|
|
82
|
+
return None
|
|
83
|
+
response.raise_for_status()
|
|
84
|
+
return response.text
|