imbi-plugin-github 0.1.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.
@@ -0,0 +1,45 @@
1
+ name: Publish to PyPI
2
+ on:
3
+ release:
4
+ types: [published]
5
+
6
+ permissions:
7
+ id-token: write
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout repository
14
+ uses: actions/checkout@v6
15
+ with:
16
+ fetch-depth: 0
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v5
20
+
21
+ - name: Set up Python
22
+ run: uv python install 3.14
23
+
24
+ - name: Build
25
+ run: uv build
26
+
27
+ - name: Upload dist
28
+ uses: actions/upload-artifact@v4
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+
33
+ publish:
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+ environment: pypi
37
+ steps:
38
+ - name: Download dist
39
+ uses: actions/download-artifact@v4
40
+ with:
41
+ name: dist
42
+ path: dist/
43
+
44
+ - name: Publish package
45
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,54 @@
1
+ name: Testing
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches: [ "main" ]
6
+ paths-ignore:
7
+ - 'docs/**'
8
+ - '*.md'
9
+ tags-ignore: [ "*" ]
10
+
11
+ jobs:
12
+ lint:
13
+ name: "Static Analysis"
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout repository
17
+ uses: actions/checkout@v5
18
+ - name: Install just
19
+ uses: extractions/setup-just@v2
20
+ with:
21
+ just-version: '1.47.1'
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v7
24
+ with:
25
+ enable-cache: true
26
+ - name: Run linters
27
+ run: just lint
28
+
29
+ test:
30
+ name: "Automated Tests"
31
+ runs-on: ubuntu-latest
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ python: [ "3.14" ]
36
+ steps:
37
+ - name: Checkout repository
38
+ uses: actions/checkout@v5
39
+ - name: Install just
40
+ uses: extractions/setup-just@v2
41
+ with:
42
+ just-version: '1.47.1'
43
+ - name: Install uv
44
+ uses: astral-sh/setup-uv@v7
45
+ with:
46
+ python-version: "${{ matrix.python }}"
47
+ enable-cache: true
48
+ - name: Test with python ${{ matrix.python }}
49
+ run: just test
50
+ - name: Upload Coverage
51
+ uses: codecov/codecov-action@v5
52
+ with:
53
+ files: ./build/coverage.xml
54
+ flags: unittests
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .pytest_cache/
5
+ .coverage
6
+ htmlcov/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ .ruff_cache/
@@ -0,0 +1,36 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+ - id: check-merge-conflict
10
+ - id: check-toml
11
+ - id: debug-statements
12
+
13
+ - repo: https://github.com/astral-sh/ruff-pre-commit
14
+ rev: v0.14.6
15
+ hooks:
16
+ - id: ruff-check
17
+ args: [--fix]
18
+ - id: ruff-format
19
+
20
+ - repo: https://github.com/tombi-toml/tombi-pre-commit
21
+ rev: v0.7.25
22
+ hooks:
23
+ - id: tombi-format
24
+
25
+ - repo: local
26
+ hooks:
27
+ - id: basedpyright
28
+ name: basedpyright
29
+ language: system
30
+ types: [python]
31
+ pass_filenames: false
32
+ entry: uv
33
+ args:
34
+ - run
35
+ - basedpyright
36
+ - src
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Imbi
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: imbi-plugin-github
3
+ Version: 0.1.0
4
+ Summary: GitHub identity plugin for Imbi (github.com / GHEC / GHES)
5
+ Author-email: "Gavin M. Roy" <gavinr@aweber.com>
6
+ License: BSD-3-Clause
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Natural Language :: English
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Python: >=3.14
15
+ Requires-Dist: httpx>=0.27
16
+ Requires-Dist: imbi-common>=0.8.4
17
+ Requires-Dist: pydantic>=2
18
+ Description-Content-Type: text/markdown
19
+
20
+ # imbi-plugin-github
21
+
22
+ GitHub identity plugin for Imbi. Ships three entry points so the admin UI
23
+ can distinguish github.com, GitHub Enterprise Cloud, and GitHub Enterprise
24
+ Server installations:
25
+
26
+ | Entry point | Slug | Host |
27
+ | -------------------------- | ----------------------------- | ----------------------------------- |
28
+ | `github` | `github` | `github.com` |
29
+ | `github-enterprise-cloud` | `github-enterprise-cloud` | `<tenant>.ghe.com` |
30
+ | `github-enterprise-server` | `github-enterprise-server` | operator-supplied via the `host` option |
31
+
32
+ Phase 1 ships the OAuth App flow only; GitHub App installation tokens are
33
+ deferred. The access token returned by the OAuth grant is passed straight
34
+ to GitHub APIs as a `Bearer` token, so `materialize()` is a no-op.
35
+
36
+ ## Manifest options
37
+
38
+ | Option | Required | Description |
39
+ | ---------------- | --------- | -------------------------------------------------------------------------- |
40
+ | `host` | GHEC/GHES | Tenant or appliance host (e.g. `tenant.ghe.com`, `github.example.com`). |
41
+ | `default_scopes` | no | Space-separated default OAuth scopes (default: `read:user user:email`). |
42
+
43
+ ## Credentials
44
+
45
+ | Field | Required |
46
+ | ---------------- | -------- |
47
+ | `client_id` | yes |
48
+ | `client_secret` | yes |
49
+
50
+ ## License
51
+
52
+ BSD-3-Clause.
@@ -0,0 +1,33 @@
1
+ # imbi-plugin-github
2
+
3
+ GitHub identity plugin for Imbi. Ships three entry points so the admin UI
4
+ can distinguish github.com, GitHub Enterprise Cloud, and GitHub Enterprise
5
+ Server installations:
6
+
7
+ | Entry point | Slug | Host |
8
+ | -------------------------- | ----------------------------- | ----------------------------------- |
9
+ | `github` | `github` | `github.com` |
10
+ | `github-enterprise-cloud` | `github-enterprise-cloud` | `<tenant>.ghe.com` |
11
+ | `github-enterprise-server` | `github-enterprise-server` | operator-supplied via the `host` option |
12
+
13
+ Phase 1 ships the OAuth App flow only; GitHub App installation tokens are
14
+ deferred. The access token returned by the OAuth grant is passed straight
15
+ to GitHub APIs as a `Bearer` token, so `materialize()` is a no-op.
16
+
17
+ ## Manifest options
18
+
19
+ | Option | Required | Description |
20
+ | ---------------- | --------- | -------------------------------------------------------------------------- |
21
+ | `host` | GHEC/GHES | Tenant or appliance host (e.g. `tenant.ghe.com`, `github.example.com`). |
22
+ | `default_scopes` | no | Space-separated default OAuth scopes (default: `read:user user:email`). |
23
+
24
+ ## Credentials
25
+
26
+ | Field | Required |
27
+ | ---------------- | -------- |
28
+ | `client_id` | yes |
29
+ | `client_secret` | yes |
30
+
31
+ ## License
32
+
33
+ BSD-3-Clause.
@@ -0,0 +1,59 @@
1
+ [default]
2
+ [private]
3
+ @help:
4
+ just --list
5
+
6
+ [private]
7
+ ci: lint test
8
+
9
+ [doc("Set up your development environment")]
10
+ [group("Environment")]
11
+ setup:
12
+ uv sync --all-groups --all-extras --frozen
13
+ uv run pre-commit install --install-hooks --overwrite
14
+
15
+ [doc("Run tests")]
16
+ [group("Testing")]
17
+ test *FILES: setup
18
+ #!/usr/bin/env sh
19
+ set -e
20
+ env_args=""
21
+ if [ -f .env ]; then
22
+ env_args="--env-file .env"
23
+ fi
24
+ if [ -z "{{ FILES }}" ]; then
25
+ uv run $env_args coverage run -m pytest tests
26
+ uv run coverage report
27
+ uv run coverage xml -o build/coverage.xml
28
+ else
29
+ uv run $env_args pytest {{ FILES }}
30
+ fi
31
+
32
+ [doc("Run linters")]
33
+ [group("Testing")]
34
+ lint: setup
35
+ uv run pre-commit run --all-files
36
+
37
+ [doc("Reformat code (optionally pass specific files)")]
38
+ [group("Development")]
39
+ format *FILES: setup
40
+ #!/usr/bin/env sh
41
+ if [ "{{ FILES }}" = '' ]; then
42
+ args='--all-files'
43
+ else
44
+ args='--files {{ FILES }}'
45
+ fi
46
+ uv run pre-commit run ruff-format $args
47
+ uv run pre-commit run tombi-format $args
48
+
49
+ [doc("Remove runtime development artifacts")]
50
+ [group("Environment")]
51
+ clean:
52
+ rm -f .coverage .env
53
+ rm -fR build
54
+
55
+ [confirm]
56
+ [doc("Remove caches, virtual env, and output files")]
57
+ [group("Environment")]
58
+ real-clean: clean
59
+ rm -fR .venv .*_cache dist
@@ -0,0 +1,124 @@
1
+ [project]
2
+ name = "imbi-plugin-github"
3
+ version = "0.1.0"
4
+ description = "GitHub identity plugin for Imbi (github.com / GHEC / GHES)"
5
+ readme = "README.md"
6
+ requires-python = ">=3.14"
7
+ license = { text = "BSD-3-Clause" }
8
+ authors = [{ name = "Gavin M. Roy", email = "gavinr@aweber.com" }]
9
+ classifiers = [
10
+ "Development Status :: 3 - Alpha",
11
+ "Intended Audience :: Developers",
12
+ "Natural Language :: English",
13
+ "Operating System :: OS Independent",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.14",
16
+ ]
17
+ dependencies = [
18
+ "httpx>=0.27",
19
+ "imbi-common>=0.8.4",
20
+ "pydantic>=2",
21
+ ]
22
+
23
+ [project.entry-points."imbi.plugins"]
24
+ github = "imbi_plugin_github.identity:GitHubPlugin"
25
+ github-enterprise-cloud = "imbi_plugin_github.identity:GitHubEnterpriseCloudPlugin"
26
+ github-enterprise-server = "imbi_plugin_github.identity:GitHubEnterpriseServerPlugin"
27
+ github-deployment = "imbi_plugin_github.deployment:GitHubDeploymentPlugin"
28
+ github-deployment-ec = "imbi_plugin_github.deployment:GitHubEnterpriseCloudDeploymentPlugin"
29
+ github-deployment-es = "imbi_plugin_github.deployment:GitHubEnterpriseServerDeploymentPlugin"
30
+
31
+ [dependency-groups]
32
+ dev = [
33
+ "basedpyright",
34
+ "coverage[toml]",
35
+ "imbi-common[server,databases]",
36
+ "pre-commit",
37
+ "pytest",
38
+ "pytest-asyncio",
39
+ "respx",
40
+ "ruff~=0.14.6",
41
+ ]
42
+ dist = ["build", "twine", "wheel"]
43
+
44
+ [build-system]
45
+ requires = ["hatchling"]
46
+ build-backend = "hatchling.build"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/imbi_plugin_github"]
50
+
51
+ [tool.coverage.report]
52
+ exclude_also = ["typing.TYPE_CHECKING"]
53
+ fail_under = 85
54
+ show_missing = true
55
+
56
+ [tool.coverage.run]
57
+ branch = true
58
+ source = ["src/imbi_plugin_github"]
59
+
60
+ [tool.pytest.ini_options]
61
+ asyncio_mode = "auto"
62
+ asyncio_default_fixture_loop_scope = "function"
63
+
64
+ [tool.pyright]
65
+ executionEnvironments = [
66
+ { root = "src" },
67
+ { root = "tests", reportPrivateUsage = false },
68
+ ]
69
+ deprecateTypingAliases = true
70
+ reportMissingSuperCall = "hint"
71
+ reportMissingTypeStubs = false
72
+ typeCheckingMode = "strict"
73
+
74
+ [tool.ruff]
75
+ line-length = 79
76
+ target-version = "py314"
77
+
78
+ [tool.ruff.format]
79
+ quote-style = "single"
80
+
81
+ [tool.ruff.lint]
82
+ select = [
83
+ "BLE",
84
+ "C4",
85
+ "C90",
86
+ "E",
87
+ "W",
88
+ "F",
89
+ "G",
90
+ "I",
91
+ "N",
92
+ "Q",
93
+ "S",
94
+ "ASYNC",
95
+ "B",
96
+ "DTZ",
97
+ "FURB",
98
+ "RUF",
99
+ "T20",
100
+ "UP",
101
+ ]
102
+ ignore = [
103
+ "ASYNC109",
104
+ "N818",
105
+ "RSE",
106
+ "S105",
107
+ "TRY003",
108
+ "TRY400",
109
+ "UP047",
110
+ ]
111
+ flake8-quotes = { inline-quotes = "single" }
112
+
113
+ [tool.ruff.lint.mccabe]
114
+ max-complexity = 15
115
+
116
+ [tool.ruff.lint.per-file-ignores]
117
+ "tests/**/*.py" = ["S"]
118
+
119
+ [tool.uv]
120
+ default-groups = ["dev"]
121
+
122
+ [[tool.uv.index]]
123
+ url = "https://pypi.org/simple"
124
+ default = true
@@ -0,0 +1,13 @@
1
+ """Imbi GitHub identity plugin (github.com / GHEC / GHES)."""
2
+
3
+ from imbi_plugin_github.identity import (
4
+ GitHubEnterpriseCloudPlugin,
5
+ GitHubEnterpriseServerPlugin,
6
+ GitHubPlugin,
7
+ )
8
+
9
+ __all__ = [
10
+ 'GitHubEnterpriseCloudPlugin',
11
+ 'GitHubEnterpriseServerPlugin',
12
+ 'GitHubPlugin',
13
+ ]
@@ -0,0 +1,50 @@
1
+ """Shared host resolution utilities for the GitHub plugins.
2
+
3
+ Identity (``plugin.py``) and deployment (``deployment.py``) plugins both
4
+ accept a ``host`` option and need to normalise it to a bare hostname
5
+ that URLs can be safely composed against. This module is the single
6
+ source of truth for that validation.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import typing
12
+ import urllib.parse
13
+
14
+
15
+ def normalize_host(raw: typing.Any, label: str) -> str:
16
+ """Validate and normalize a manifest ``host`` value.
17
+
18
+ Strips whitespace, accepts an optional scheme, and rejects values
19
+ with paths / queries / fragments so callers can compose URLs from
20
+ the result without producing malformed endpoints.
21
+ """
22
+ host = str(raw or '').strip()
23
+ if not host:
24
+ raise ValueError(f'{label} requires the "host" option')
25
+ parsed = urllib.parse.urlsplit(
26
+ host if '://' in host else f'https://{host}'
27
+ )
28
+ if (
29
+ not parsed.hostname
30
+ or parsed.port is not None
31
+ or parsed.path not in ('', '/')
32
+ or parsed.query
33
+ or parsed.fragment
34
+ ):
35
+ raise ValueError(f'{label} got invalid host value: {host!r}')
36
+ return parsed.hostname
37
+
38
+
39
+ def require_ghec_tenant_host(host: str, label: str) -> str:
40
+ """Refuse anything that isn't a ``*.ghe.com`` tenant host."""
41
+ if (
42
+ not host.endswith('.ghe.com')
43
+ or host == '.ghe.com'
44
+ or host.startswith('api.')
45
+ ):
46
+ raise ValueError(
47
+ f'{label} requires a tenant host like "tenant.ghe.com"; '
48
+ f'got {host!r}'
49
+ )
50
+ return host