lex-github 0.2.5 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/CLAUDE.md +45 -19
- data/README.md +155 -83
- data/lex-github.gemspec +2 -0
- data/lib/legion/extensions/github/app/actor/token_refresh.rb +68 -0
- data/lib/legion/extensions/github/app/actor/webhook_poller.rb +65 -0
- data/lib/legion/extensions/github/app/hooks/setup.rb +19 -0
- data/lib/legion/extensions/github/app/hooks/webhook.rb +19 -0
- data/lib/legion/extensions/github/app/runners/auth.rb +48 -0
- data/lib/legion/extensions/github/app/runners/credential_store.rb +46 -0
- data/lib/legion/extensions/github/app/runners/installations.rb +56 -0
- data/lib/legion/extensions/github/app/runners/manifest.rb +65 -0
- data/lib/legion/extensions/github/app/runners/webhooks.rb +118 -0
- data/lib/legion/extensions/github/app/transport/exchanges/app.rb +17 -0
- data/lib/legion/extensions/github/app/transport/messages/event.rb +18 -0
- data/lib/legion/extensions/github/app/transport/queues/auth.rb +18 -0
- data/lib/legion/extensions/github/app/transport/queues/webhooks.rb +18 -0
- data/lib/legion/extensions/github/cli/app.rb +57 -0
- data/lib/legion/extensions/github/cli/auth.rb +99 -0
- data/lib/legion/extensions/github/client.rb +24 -0
- data/lib/legion/extensions/github/errors.rb +44 -0
- data/lib/legion/extensions/github/helpers/browser_auth.rb +106 -0
- data/lib/legion/extensions/github/helpers/cache.rb +99 -0
- data/lib/legion/extensions/github/helpers/callback_server.rb +89 -0
- data/lib/legion/extensions/github/helpers/client.rb +292 -2
- data/lib/legion/extensions/github/helpers/scope_registry.rb +91 -0
- data/lib/legion/extensions/github/helpers/token_cache.rb +86 -0
- data/lib/legion/extensions/github/middleware/credential_fallback.rb +76 -0
- data/lib/legion/extensions/github/middleware/rate_limit.rb +40 -0
- data/lib/legion/extensions/github/middleware/scope_probe.rb +37 -0
- data/lib/legion/extensions/github/oauth/actor/token_refresh.rb +76 -0
- data/lib/legion/extensions/github/oauth/hooks/callback.rb +19 -0
- data/lib/legion/extensions/github/oauth/runners/auth.rb +111 -0
- data/lib/legion/extensions/github/oauth/transport/exchanges/oauth.rb +17 -0
- data/lib/legion/extensions/github/oauth/transport/queues/auth.rb +18 -0
- data/lib/legion/extensions/github/runners/actions.rb +100 -0
- data/lib/legion/extensions/github/runners/branches.rb +5 -3
- data/lib/legion/extensions/github/runners/checks.rb +84 -0
- data/lib/legion/extensions/github/runners/comments.rb +13 -7
- data/lib/legion/extensions/github/runners/commits.rb +11 -6
- data/lib/legion/extensions/github/runners/contents.rb +3 -1
- data/lib/legion/extensions/github/runners/deployments.rb +76 -0
- data/lib/legion/extensions/github/runners/gists.rb +9 -4
- data/lib/legion/extensions/github/runners/issues.rb +16 -9
- data/lib/legion/extensions/github/runners/labels.rb +16 -9
- data/lib/legion/extensions/github/runners/organizations.rb +10 -8
- data/lib/legion/extensions/github/runners/pull_requests.rb +24 -14
- data/lib/legion/extensions/github/runners/releases.rb +89 -0
- data/lib/legion/extensions/github/runners/repositories.rb +17 -10
- data/lib/legion/extensions/github/runners/repository_webhooks.rb +76 -0
- data/lib/legion/extensions/github/runners/search.rb +11 -8
- data/lib/legion/extensions/github/runners/users.rb +12 -8
- data/lib/legion/extensions/github/version.rb +1 -1
- data/lib/legion/extensions/github.rb +22 -0
- metadata +63 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 528c32f26f0baa590aa8084482cf2c006633669ed182df0e2288198fb509deb4
|
|
4
|
+
data.tar.gz: 453260cf722e2b8463816e4b9227ac2e6c70996063f68886f062e9710b01eda0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4060b46c5f83856954a7c49e542351558202f855fe0d471a1984d59522e4d178fe78c2bfcc58d8a254b52bff4e763ff3ec233b16a8d2046fbbeb2b44cf089f4c
|
|
7
|
+
data.tar.gz: 7d148064e9e9b5225589f6c0d73e30938bc2d56beea9d4f89f2cb0443a2cc03d71f48fbe0b14a8a74ff150e44c4e94450185877c2539127da9ca2daa99507e2a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- GitHub App authentication (JWT generation, installation tokens, manifest flow)
|
|
7
|
+
- OAuth delegated authentication (Authorization Code + PKCE, device code fallback)
|
|
8
|
+
- Scope-aware credential resolution chain (8 sources, rate limit + scope fallback)
|
|
9
|
+
- `ScopeRegistry` for caching credential-to-owner/repo authorization status
|
|
10
|
+
- `CredentialFallback` Faraday middleware (transparent 403/429 retry with next credential)
|
|
11
|
+
- `RateLimit` Faraday middleware with automatic credential exhaustion tracking
|
|
12
|
+
- `ScopeProbe` Faraday middleware for passive scope learning from API responses
|
|
13
|
+
- `Helpers::Cache` for two-tier API response caching (global Redis + local in-memory)
|
|
14
|
+
- `Helpers::TokenCache` for token lifecycle management with per-installation keying
|
|
15
|
+
- `App::Runners::Auth` (JWT generation, installation token exchange)
|
|
16
|
+
- `App::Runners::Webhooks` (signature verification, event parsing, scope invalidation)
|
|
17
|
+
- `App::Runners::Manifest` (GitHub App manifest flow)
|
|
18
|
+
- `App::Runners::Installations` (list, get, suspend, unsuspend, delete)
|
|
19
|
+
- `App::Runners::CredentialStore` (Vault persistence after manifest flow)
|
|
20
|
+
- `OAuth::Runners::Auth` (authorize_url, exchange_code, refresh, device_code, revoke)
|
|
21
|
+
- `Runners::Actions` (GitHub Actions workflow management)
|
|
22
|
+
- `Runners::Checks` (check runs and check suites)
|
|
23
|
+
- `Runners::Releases` (release and asset management)
|
|
24
|
+
- `Runners::Deployments` (deployment and status management)
|
|
25
|
+
- `Runners::RepositoryWebhooks` (programmatic webhook management)
|
|
26
|
+
- `Helpers::CallbackServer` for standalone OAuth redirect handling
|
|
27
|
+
- `Helpers::BrowserAuth` for browser-based OAuth with PKCE
|
|
28
|
+
- `CLI::Auth` for `legion lex exec github auth login/status`
|
|
29
|
+
- `CLI::App` for `legion lex exec github app setup`
|
|
30
|
+
- `RateLimitError`, `AuthorizationError`, `ScopeDeniedError` error classes
|
|
31
|
+
- `jwt` (~> 2.7) and `base64` (>= 0.1) runtime dependencies
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- `Helpers::Client` now uses scope-aware credential resolution (`owner:`, `repo:` context)
|
|
35
|
+
- All existing runners forward `owner:` and `repo:` to `connection()` for scope-aware resolution
|
|
36
|
+
- All existing runners now include `Helpers::Cache` for two-tier API response caching
|
|
37
|
+
- `Client` class includes App and OAuth runner modules
|
|
38
|
+
- Version bump to 0.3.0
|
|
39
|
+
|
|
40
|
+
## [0.3.0] - 2026-03-30
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- GitHub App authentication (JWT generation, installation tokens via `App::Runners::Auth`)
|
|
44
|
+
- OAuth delegated user authentication (Authorization Code + PKCE, device code flow via `OAuth::Runners::Auth`)
|
|
45
|
+
- GitHub App manifest flow for streamlined app registration (`App::Runners::Manifest`)
|
|
46
|
+
- Webhook signature verification and event parsing (`App::Runners::Webhooks`)
|
|
47
|
+
- 8-source credential resolution chain: Vault delegated → Settings delegated → Vault App → Settings App → Vault PAT → Settings PAT → GH CLI → ENV (`Helpers::Client`)
|
|
48
|
+
- Rate limit fallback across credential sources with scope-aware skipping (`Helpers::ScopeRegistry`)
|
|
49
|
+
- Token lifecycle management with expiry tracking and rate limit recording (`Helpers::TokenCache`)
|
|
50
|
+
- Two-tier API response caching (global Redis + local in-memory) with configurable per-resource TTLs (`Helpers::Cache`)
|
|
51
|
+
- `jwt` (~> 2.7) and `base64` (>= 0.1) runtime dependencies
|
|
52
|
+
|
|
3
53
|
## [0.2.5] - 2026-03-30
|
|
4
54
|
|
|
5
55
|
### Changed
|
data/CLAUDE.md
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
8
8
|
|
|
9
|
-
Legion Extension that connects LegionIO to GitHub. Provides runners for interacting with the GitHub REST API covering repositories, issues, pull requests, users, organizations, gists, search, labels, comments, commits, branches, and
|
|
9
|
+
Legion Extension that connects LegionIO to GitHub. Provides runners for interacting with the GitHub REST API covering repositories, issues, pull requests, users, organizations, gists, search, labels, comments, commits, branches, file contents, GitHub App authentication, OAuth delegated auth, and webhook handling.
|
|
10
10
|
|
|
11
11
|
**GitHub**: https://github.com/LegionIO/lex-github
|
|
12
12
|
**License**: MIT
|
|
13
|
-
**Version**: 0.
|
|
13
|
+
**Version**: 0.3.0
|
|
14
14
|
|
|
15
15
|
## Architecture
|
|
16
16
|
|
|
@@ -29,40 +29,66 @@ Legion::Extensions::Github
|
|
|
29
29
|
│ ├── Commits # List, get, compare commits
|
|
30
30
|
│ ├── Branches # Create branches via Git Data API
|
|
31
31
|
│ └── Contents # Commit multiple files via Git Data API
|
|
32
|
+
├── App/
|
|
33
|
+
│ └── Runners/
|
|
34
|
+
│ ├── Auth # JWT generation, installation token exchange, list/get installations
|
|
35
|
+
│ ├── Webhooks # HMAC signature verification, event parsing
|
|
36
|
+
│ ├── Manifest # GitHub App manifest flow (generate, exchange code, manifest URL)
|
|
37
|
+
│ └── Installations # Full installation management (list repos, suspend, delete)
|
|
38
|
+
├── OAuth/
|
|
39
|
+
│ └── Runners/
|
|
40
|
+
│ └── Auth # PKCE + Authorization Code, device code, refresh, revoke
|
|
32
41
|
├── Helpers/
|
|
33
|
-
│
|
|
42
|
+
│ ├── Client # 8-source scope-aware credential resolution chain + Faraday builder
|
|
43
|
+
│ ├── Cache # Two-tier read-through/write-through API response caching
|
|
44
|
+
│ ├── TokenCache # Token lifecycle management (store, fetch, expiry, rate limits)
|
|
45
|
+
│ └── ScopeRegistry # Credential-to-scope authorization cache (org/repo level)
|
|
34
46
|
└── Client # Standalone client class (includes all runners)
|
|
35
47
|
```
|
|
36
48
|
|
|
49
|
+
### Credential Resolution Chain (8 sources, in priority order)
|
|
50
|
+
|
|
51
|
+
1. `resolve_vault_delegated` — OAuth user token from Vault (`github/oauth/delegated/token`)
|
|
52
|
+
2. `resolve_settings_delegated` — OAuth user token from `Legion::Settings[:github][:oauth][:access_token]`
|
|
53
|
+
3. `resolve_vault_app` — GitHub App installation token (requires cached token from `TokenCache`)
|
|
54
|
+
4. `resolve_settings_app` — App token from settings (requires cached token)
|
|
55
|
+
5. `resolve_vault_pat` — PAT from Vault (`github/token`)
|
|
56
|
+
6. `resolve_settings_pat` — PAT from `Legion::Settings[:github][:token]`
|
|
57
|
+
7. `resolve_gh_cli` — Token from `gh auth token` CLI command (cached 300s)
|
|
58
|
+
8. `resolve_env` — `GITHUB_TOKEN` environment variable
|
|
59
|
+
|
|
60
|
+
Rate-limited credentials are skipped. Scope-denied credentials (for a given owner/repo) are skipped.
|
|
61
|
+
|
|
37
62
|
## Dependencies
|
|
38
63
|
|
|
39
64
|
| Gem | Purpose |
|
|
40
65
|
|-----|---------|
|
|
41
66
|
| `faraday` | HTTP client for GitHub REST API |
|
|
67
|
+
| `jwt` (~> 2.7) | RS256 JWT generation for GitHub App authentication |
|
|
68
|
+
| `base64` (>= 0.1) | PKCE code challenge computation |
|
|
69
|
+
| `legion-cache` | Two-tier caching (global Redis + local in-memory) |
|
|
70
|
+
| `legion-crypt` | Vault secret resolution for credentials |
|
|
71
|
+
| `legion-settings` | Settings-based credential resolution |
|
|
42
72
|
|
|
43
73
|
## Key Files
|
|
44
74
|
|
|
45
75
|
| File | Purpose |
|
|
46
76
|
|------|---------|
|
|
47
|
-
| `lib/legion/extensions/github.rb` | Extension entry point, requires all
|
|
48
|
-
| `lib/legion/extensions/github/client.rb` | Standalone client class |
|
|
49
|
-
| `lib/legion/extensions/github/helpers/client.rb` | Faraday
|
|
50
|
-
| `lib/legion/extensions/github/
|
|
51
|
-
| `lib/legion/extensions/github/
|
|
52
|
-
| `lib/legion/extensions/github/
|
|
53
|
-
| `lib/legion/extensions/github/runners/
|
|
54
|
-
| `lib/legion/extensions/github/runners/
|
|
55
|
-
| `lib/legion/extensions/github/runners/
|
|
56
|
-
| `lib/legion/extensions/github/runners/
|
|
57
|
-
| `lib/legion/extensions/github/runners/
|
|
58
|
-
| `lib/legion/extensions/github/runners/comments.rb` | Issue/PR comment CRUD |
|
|
59
|
-
| `lib/legion/extensions/github/runners/commits.rb` | List, get, compare commits |
|
|
60
|
-
| `lib/legion/extensions/github/runners/branches.rb` | Create branches via Git Data API |
|
|
61
|
-
| `lib/legion/extensions/github/runners/contents.rb` | Commit multiple files via Git Data API |
|
|
77
|
+
| `lib/legion/extensions/github.rb` | Extension entry point, requires all modules |
|
|
78
|
+
| `lib/legion/extensions/github/client.rb` | Standalone client class (includes all runners) |
|
|
79
|
+
| `lib/legion/extensions/github/helpers/client.rb` | Credential resolution chain + Faraday builder |
|
|
80
|
+
| `lib/legion/extensions/github/helpers/cache.rb` | Two-tier API response caching |
|
|
81
|
+
| `lib/legion/extensions/github/helpers/token_cache.rb` | Token lifecycle + rate limit tracking |
|
|
82
|
+
| `lib/legion/extensions/github/helpers/scope_registry.rb` | Credential-to-scope authorization cache |
|
|
83
|
+
| `lib/legion/extensions/github/app/runners/auth.rb` | JWT generation, installation tokens |
|
|
84
|
+
| `lib/legion/extensions/github/app/runners/webhooks.rb` | Webhook signature verification, event parsing |
|
|
85
|
+
| `lib/legion/extensions/github/app/runners/manifest.rb` | GitHub App manifest registration flow |
|
|
86
|
+
| `lib/legion/extensions/github/app/runners/installations.rb` | Installation management |
|
|
87
|
+
| `lib/legion/extensions/github/oauth/runners/auth.rb` | OAuth PKCE, device code, token refresh/revoke |
|
|
62
88
|
|
|
63
89
|
## Testing
|
|
64
90
|
|
|
65
|
-
|
|
91
|
+
131 specs across 23 spec files (growing with each new runner).
|
|
66
92
|
|
|
67
93
|
```bash
|
|
68
94
|
bundle install
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# lex-github
|
|
2
2
|
|
|
3
|
-
GitHub integration for [LegionIO](https://github.com/LegionIO/LegionIO). Provides runners for interacting with the GitHub REST API including repositories, issues, pull requests, labels, comments, commits, users, organizations, gists, and
|
|
3
|
+
GitHub integration for [LegionIO](https://github.com/LegionIO/LegionIO). Provides runners for interacting with the GitHub REST API including repositories, issues, pull requests, labels, comments, commits, users, organizations, gists, search, Actions workflows, checks, releases, deployments, webhooks, and full GitHub App + OAuth authentication.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,6 +8,62 @@ GitHub integration for [LegionIO](https://github.com/LegionIO/LegionIO). Provide
|
|
|
8
8
|
gem install lex-github
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Authentication
|
|
12
|
+
|
|
13
|
+
### Personal Access Token (PAT)
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
client = Legion::Extensions::Github::Client.new(token: 'ghp_your_token')
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### GitHub App (JWT + Installation Token)
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# Set in Legion::Settings
|
|
23
|
+
# github.app.app_id: '12345'
|
|
24
|
+
# github.app.private_key_path: '/path/to/private-key.pem'
|
|
25
|
+
# github.app.installation_id: '67890'
|
|
26
|
+
|
|
27
|
+
client = Legion::Extensions::Github::Client.new
|
|
28
|
+
# Credentials resolved automatically from settings
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or via Vault:
|
|
32
|
+
```
|
|
33
|
+
vault write secret/github/app/app_id value='12345'
|
|
34
|
+
vault write secret/github/app/private_key value='-----BEGIN RSA PRIVATE KEY-----...'
|
|
35
|
+
vault write secret/github/app/installation_id value='67890'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### OAuth Delegated (browser-based login)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# CLI login
|
|
42
|
+
legion lex exec github auth login
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# Programmatic (using the CLI::Auth mixin)
|
|
47
|
+
auth = Object.new.extend(Legion::Extensions::Github::CLI::Auth)
|
|
48
|
+
result = auth.login(client_id: 'Iv1.abc', client_secret: 'secret')
|
|
49
|
+
# Opens browser → PKCE flow; token can be stored in Vault if Vault integration is configured
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Credential Resolution Chain
|
|
53
|
+
|
|
54
|
+
lex-github resolves credentials automatically in priority order:
|
|
55
|
+
|
|
56
|
+
1. Vault OAuth delegated token
|
|
57
|
+
2. Settings OAuth access token
|
|
58
|
+
3. Vault GitHub App installation token (auto-generates on miss)
|
|
59
|
+
4. Settings GitHub App installation token
|
|
60
|
+
5. Vault PAT
|
|
61
|
+
6. Settings PAT (`github.token`)
|
|
62
|
+
7. `gh` CLI token (`gh auth token`)
|
|
63
|
+
8. `GITHUB_TOKEN` environment variable
|
|
64
|
+
|
|
65
|
+
Rate-limited credentials are skipped automatically. Scope-denied credentials (`403`) are skipped for the specific owner/repo and retried with the next source.
|
|
66
|
+
|
|
11
67
|
## Standalone Usage
|
|
12
68
|
|
|
13
69
|
```ruby
|
|
@@ -28,117 +84,133 @@ client.create_issue(owner: 'octocat', repo: 'Hello-World', title: 'Bug report')
|
|
|
28
84
|
client.list_pull_requests(owner: 'octocat', repo: 'Hello-World')
|
|
29
85
|
client.create_pull_request(owner: 'octocat', repo: 'Hello-World', title: 'Fix', head: 'fix-branch', base: 'main')
|
|
30
86
|
client.merge_pull_request(owner: 'octocat', repo: 'Hello-World', pull_number: 42)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
client.
|
|
35
|
-
client.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
client.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
client.
|
|
58
|
-
client.
|
|
87
|
+
|
|
88
|
+
# GitHub Actions
|
|
89
|
+
client.list_workflows(owner: 'octocat', repo: 'Hello-World')
|
|
90
|
+
client.trigger_workflow(owner: 'octocat', repo: 'Hello-World', workflow_id: 'ci.yml', ref: 'main')
|
|
91
|
+
client.get_workflow_run(owner: 'octocat', repo: 'Hello-World', run_id: 12345)
|
|
92
|
+
|
|
93
|
+
# Check Runs (CI status)
|
|
94
|
+
client.create_check_run(owner: 'octocat', repo: 'Hello-World', name: 'CI', head_sha: 'abc123')
|
|
95
|
+
client.update_check_run(owner: 'octocat', repo: 'Hello-World', check_run_id: 1,
|
|
96
|
+
status: 'completed', conclusion: 'success')
|
|
97
|
+
|
|
98
|
+
# Releases
|
|
99
|
+
client.list_releases(owner: 'octocat', repo: 'Hello-World')
|
|
100
|
+
client.create_release(owner: 'octocat', repo: 'Hello-World', tag_name: 'v1.0.0')
|
|
101
|
+
|
|
102
|
+
# Deployments
|
|
103
|
+
client.create_deployment(owner: 'octocat', repo: 'Hello-World', ref: 'main', environment: 'production')
|
|
104
|
+
client.create_deployment_status(owner: 'octocat', repo: 'Hello-World', deployment_id: 1, state: 'success')
|
|
105
|
+
|
|
106
|
+
# Webhooks
|
|
107
|
+
client.list_webhooks(owner: 'octocat', repo: 'Hello-World')
|
|
108
|
+
client.create_webhook(owner: 'octocat', repo: 'Hello-World',
|
|
109
|
+
config: { url: 'https://example.com/webhook', content_type: 'json' })
|
|
110
|
+
|
|
111
|
+
# GitHub App
|
|
112
|
+
jwt_token = client.generate_jwt(app_id: '12345', private_key: File.read('private-key.pem'))
|
|
113
|
+
client.create_installation_token(jwt: jwt_token, installation_id: '67890')
|
|
114
|
+
client.list_installations(jwt: jwt_token)
|
|
115
|
+
|
|
116
|
+
# Webhook verification
|
|
117
|
+
client.verify_signature(payload: request.body.read, signature: request.env['HTTP_X_HUB_SIGNATURE_256'],
|
|
118
|
+
secret: 'webhook_secret')
|
|
59
119
|
```
|
|
60
120
|
|
|
61
121
|
## Functions
|
|
62
122
|
|
|
63
123
|
### Repositories
|
|
64
|
-
- `list_repos`
|
|
65
|
-
- `get_repo` - Get a single repository
|
|
66
|
-
- `create_repo` - Create a new repository
|
|
67
|
-
- `update_repo` - Update repository settings
|
|
68
|
-
- `delete_repo` - Delete a repository
|
|
69
|
-
- `list_branches` - List branches
|
|
70
|
-
- `list_tags` - List tags
|
|
124
|
+
- `list_repos`, `get_repo`, `create_repo`, `update_repo`, `delete_repo`, `list_branches`, `list_tags`
|
|
71
125
|
|
|
72
126
|
### Issues
|
|
73
|
-
- `list_issues`
|
|
74
|
-
- `get_issue` - Get a single issue
|
|
75
|
-
- `create_issue` - Create a new issue
|
|
76
|
-
- `update_issue` - Update an issue
|
|
77
|
-
- `list_issue_comments` - List comments on an issue
|
|
78
|
-
- `create_issue_comment` - Create a comment on an issue
|
|
127
|
+
- `list_issues`, `get_issue`, `create_issue`, `update_issue`, `list_issue_comments`, `create_issue_comment`
|
|
79
128
|
|
|
80
129
|
### Pull Requests
|
|
81
|
-
- `list_pull_requests`
|
|
82
|
-
- `
|
|
83
|
-
- `create_pull_request` - Create a pull request
|
|
84
|
-
- `update_pull_request` - Update a pull request
|
|
85
|
-
- `merge_pull_request` - Merge a pull request
|
|
86
|
-
- `list_pull_request_commits` - List commits on a PR
|
|
87
|
-
- `list_pull_request_files` - List files changed in a PR
|
|
88
|
-
- `list_pull_request_reviews` - List reviews on a PR
|
|
130
|
+
- `list_pull_requests`, `get_pull_request`, `create_pull_request`, `update_pull_request`, `merge_pull_request`
|
|
131
|
+
- `list_pull_request_commits`, `list_pull_request_files`, `list_pull_request_reviews`, `create_review`
|
|
89
132
|
|
|
90
133
|
### Labels
|
|
91
|
-
- `list_labels`
|
|
92
|
-
- `
|
|
93
|
-
- `create_label` - Create a new label
|
|
94
|
-
- `update_label` - Update a label
|
|
95
|
-
- `delete_label` - Delete a label
|
|
96
|
-
- `add_labels_to_issue` - Add labels to an issue
|
|
97
|
-
- `remove_label_from_issue` - Remove a label from an issue
|
|
134
|
+
- `list_labels`, `get_label`, `create_label`, `update_label`, `delete_label`
|
|
135
|
+
- `add_labels_to_issue`, `remove_label_from_issue`
|
|
98
136
|
|
|
99
137
|
### Comments
|
|
100
|
-
- `list_comments`
|
|
101
|
-
- `get_comment` - Get a single comment by ID
|
|
102
|
-
- `create_comment` - Create a comment on an issue or PR
|
|
103
|
-
- `update_comment` - Update a comment
|
|
104
|
-
- `delete_comment` - Delete a comment
|
|
138
|
+
- `list_comments`, `get_comment`, `create_comment`, `update_comment`, `delete_comment`
|
|
105
139
|
|
|
106
140
|
### Users
|
|
107
|
-
- `get_authenticated_user`
|
|
108
|
-
- `get_user` - Get a user by username
|
|
109
|
-
- `list_followers` - List followers
|
|
110
|
-
- `list_following` - List following
|
|
141
|
+
- `get_authenticated_user`, `get_user`, `list_followers`, `list_following`
|
|
111
142
|
|
|
112
143
|
### Organizations
|
|
113
|
-
- `list_user_orgs`
|
|
114
|
-
- `get_org` - Get an organization
|
|
115
|
-
- `list_org_repos` - List repos in an organization
|
|
116
|
-
- `list_org_members` - List organization members
|
|
144
|
+
- `list_user_orgs`, `get_org`, `list_org_repos`, `list_org_members`
|
|
117
145
|
|
|
118
146
|
### Gists
|
|
119
|
-
- `list_gists`
|
|
120
|
-
- `get_gist` - Get a single gist
|
|
121
|
-
- `create_gist` - Create a gist
|
|
122
|
-
- `update_gist` - Update a gist
|
|
123
|
-
- `delete_gist` - Delete a gist
|
|
147
|
+
- `list_gists`, `get_gist`, `create_gist`, `update_gist`, `delete_gist`
|
|
124
148
|
|
|
125
149
|
### Search
|
|
126
|
-
- `search_repositories`
|
|
127
|
-
- `search_issues` - Search issues and PRs
|
|
128
|
-
- `search_users` - Search users
|
|
129
|
-
- `search_code` - Search code
|
|
150
|
+
- `search_repositories`, `search_issues`, `search_users`, `search_code`
|
|
130
151
|
|
|
131
152
|
### Commits
|
|
132
|
-
- `list_commits`
|
|
133
|
-
|
|
134
|
-
|
|
153
|
+
- `list_commits`, `get_commit`, `compare_commits`
|
|
154
|
+
|
|
155
|
+
### Branches
|
|
156
|
+
- `create_branch`
|
|
157
|
+
|
|
158
|
+
### Contents
|
|
159
|
+
- `commit_files`
|
|
160
|
+
|
|
161
|
+
### GitHub Actions
|
|
162
|
+
- `list_workflows`, `get_workflow`, `list_workflow_runs`, `get_workflow_run`, `trigger_workflow`
|
|
163
|
+
- `cancel_workflow_run`, `rerun_workflow`, `rerun_failed_jobs`
|
|
164
|
+
- `list_workflow_run_jobs`, `download_workflow_run_logs`, `list_workflow_run_artifacts`
|
|
165
|
+
|
|
166
|
+
### Checks
|
|
167
|
+
- `create_check_run`, `update_check_run`, `get_check_run`
|
|
168
|
+
- `list_check_runs_for_ref`, `list_check_suites_for_ref`, `get_check_suite`
|
|
169
|
+
- `rerequest_check_suite`, `list_check_run_annotations`
|
|
170
|
+
|
|
171
|
+
### Releases
|
|
172
|
+
- `list_releases`, `get_release`, `get_latest_release`, `get_release_by_tag`
|
|
173
|
+
- `create_release`, `update_release`, `delete_release`
|
|
174
|
+
- `list_release_assets`, `delete_release_asset`
|
|
175
|
+
|
|
176
|
+
### Deployments
|
|
177
|
+
- `list_deployments`, `get_deployment`, `create_deployment`, `delete_deployment`
|
|
178
|
+
- `list_deployment_statuses`, `create_deployment_status`, `get_deployment_status`
|
|
179
|
+
|
|
180
|
+
### Repository Webhooks
|
|
181
|
+
- `list_webhooks`, `get_webhook`, `create_webhook`, `update_webhook`, `delete_webhook`
|
|
182
|
+
- `ping_webhook`, `test_webhook`, `list_webhook_deliveries`
|
|
183
|
+
|
|
184
|
+
### GitHub App Auth
|
|
185
|
+
- `generate_jwt`, `create_installation_token`, `list_installations`, `get_installation`
|
|
186
|
+
- `generate_manifest`, `exchange_manifest_code`, `manifest_url`
|
|
187
|
+
- `verify_signature`, `parse_event`, `receive_event`
|
|
188
|
+
|
|
189
|
+
### OAuth
|
|
190
|
+
- `generate_pkce`, `authorize_url`, `exchange_code`, `refresh_token`
|
|
191
|
+
- `request_device_code`, `poll_device_code`, `revoke_token`
|
|
192
|
+
|
|
193
|
+
## Error Handling
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
begin
|
|
197
|
+
client.get_repo(owner: 'org', repo: 'private-repo')
|
|
198
|
+
rescue Legion::Extensions::Github::RateLimitError => e
|
|
199
|
+
puts "Rate limited, resets at: #{e.reset_at}"
|
|
200
|
+
rescue Legion::Extensions::Github::ScopeDeniedError => e
|
|
201
|
+
puts "No credential authorized for #{e.owner}/#{e.repo}"
|
|
202
|
+
rescue Legion::Extensions::Github::AuthorizationError => e
|
|
203
|
+
puts "All credentials exhausted: #{e.attempted_sources}"
|
|
204
|
+
end
|
|
205
|
+
```
|
|
135
206
|
|
|
136
207
|
## Requirements
|
|
137
208
|
|
|
138
209
|
- Ruby >= 3.4
|
|
139
210
|
- [LegionIO](https://github.com/LegionIO/LegionIO) framework (optional for standalone client usage)
|
|
140
|
-
- GitHub personal access token or app token
|
|
141
211
|
- `faraday` >= 2.0
|
|
212
|
+
- `jwt` ~> 2.7 (for GitHub App authentication)
|
|
213
|
+
- `base64` >= 0.1 (for OAuth PKCE)
|
|
142
214
|
|
|
143
215
|
## License
|
|
144
216
|
|
data/lex-github.gemspec
CHANGED
|
@@ -26,7 +26,9 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
end
|
|
27
27
|
spec.require_paths = ['lib']
|
|
28
28
|
|
|
29
|
+
spec.add_dependency 'base64', '>= 0.1'
|
|
29
30
|
spec.add_dependency 'faraday', '>= 2.0'
|
|
31
|
+
spec.add_dependency 'jwt', '~> 2.7'
|
|
30
32
|
spec.add_dependency 'legion-cache', '>= 1.3.11'
|
|
31
33
|
spec.add_dependency 'legion-crypt', '>= 1.4.9'
|
|
32
34
|
spec.add_dependency 'legion-data', '>= 1.4.17'
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Github
|
|
6
|
+
module App
|
|
7
|
+
module Actor
|
|
8
|
+
class TokenRefresh < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/SelfContainedActorRunnerClass,Legion/Extension/EveryActorRequiresTime
|
|
9
|
+
def use_runner? = false
|
|
10
|
+
def check_subtask? = false
|
|
11
|
+
def generate_task? = false
|
|
12
|
+
|
|
13
|
+
def time
|
|
14
|
+
45 * 60
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# rubocop:disable Legion/Extension/ActorEnabledSideEffects
|
|
18
|
+
def enabled?
|
|
19
|
+
defined?(Legion::Extensions::Github::Helpers::TokenCache)
|
|
20
|
+
rescue StandardError => _e
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
# rubocop:enable Legion/Extension/ActorEnabledSideEffects
|
|
24
|
+
|
|
25
|
+
def manual
|
|
26
|
+
log.info('App::Actor::TokenRefresh: refreshing installation token')
|
|
27
|
+
settings = github_app_settings
|
|
28
|
+
return unless settings[:app_id] && settings[:private_key] && settings[:installation_id]
|
|
29
|
+
|
|
30
|
+
auth = Object.new.extend(Legion::Extensions::Github::App::Runners::Auth)
|
|
31
|
+
jwt_result = auth.generate_jwt(app_id: settings[:app_id], private_key: settings[:private_key])
|
|
32
|
+
return unless jwt_result[:result]
|
|
33
|
+
|
|
34
|
+
token_result = auth.create_installation_token(
|
|
35
|
+
jwt: jwt_result[:result],
|
|
36
|
+
installation_id: settings[:installation_id]
|
|
37
|
+
)
|
|
38
|
+
return unless token_result.dig(:result, 'token')
|
|
39
|
+
|
|
40
|
+
token_cache.store_token(
|
|
41
|
+
token: token_result[:result]['token'],
|
|
42
|
+
auth_type: :app_installation,
|
|
43
|
+
expires_at: Time.parse(token_result[:result]['expires_at'])
|
|
44
|
+
)
|
|
45
|
+
log.info('App::Actor::TokenRefresh: installation token refreshed')
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
log.error("App::Actor::TokenRefresh: #{e.message}")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def github_app_settings
|
|
53
|
+
return {} unless defined?(Legion::Settings)
|
|
54
|
+
|
|
55
|
+
Legion::Settings[:github]&.dig(:app) || {}
|
|
56
|
+
rescue StandardError => _e
|
|
57
|
+
{}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def token_cache
|
|
61
|
+
Object.new.extend(Legion::Extensions::Github::Helpers::TokenCache)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Github
|
|
6
|
+
module App
|
|
7
|
+
module Actor
|
|
8
|
+
class WebhookPoller < Legion::Extensions::Actors::Poll # rubocop:disable Legion/Extension/SelfContainedActorRunnerClass,Legion/Extension/EveryActorRequiresTime
|
|
9
|
+
def use_runner? = false
|
|
10
|
+
def check_subtask? = false
|
|
11
|
+
def generate_task? = false
|
|
12
|
+
|
|
13
|
+
def time
|
|
14
|
+
60
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# rubocop:disable Legion/Extension/ActorEnabledSideEffects
|
|
18
|
+
def enabled?
|
|
19
|
+
github_poll_settings[:owner] && github_poll_settings[:repo]
|
|
20
|
+
rescue StandardError => _e
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
# rubocop:enable Legion/Extension/ActorEnabledSideEffects
|
|
24
|
+
|
|
25
|
+
def manual
|
|
26
|
+
settings = github_poll_settings
|
|
27
|
+
owner = settings[:owner]
|
|
28
|
+
repo = settings[:repo]
|
|
29
|
+
return unless owner && repo
|
|
30
|
+
|
|
31
|
+
client = Legion::Extensions::Github::Client.new
|
|
32
|
+
return unless client.respond_to?(:list_events)
|
|
33
|
+
|
|
34
|
+
result = client.list_events(owner: owner, repo: repo)
|
|
35
|
+
events = result[:result]
|
|
36
|
+
return unless events.is_a?(Array)
|
|
37
|
+
|
|
38
|
+
events.each do |event|
|
|
39
|
+
publish_event(event)
|
|
40
|
+
end
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
log.error("App::Actor::WebhookPoller: #{e.message}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def github_poll_settings
|
|
48
|
+
return {} unless defined?(Legion::Settings)
|
|
49
|
+
|
|
50
|
+
Legion::Settings[:github]&.dig(:webhook_poller) || {}
|
|
51
|
+
rescue StandardError => _e
|
|
52
|
+
{}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def publish_event(event)
|
|
56
|
+
Legion::Extensions::Github::App::Transport::Messages::Event.new(event).publish
|
|
57
|
+
rescue StandardError => e
|
|
58
|
+
log.warn("WebhookPoller#publish_event: #{e.message}")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Github
|
|
6
|
+
module App
|
|
7
|
+
module Hooks
|
|
8
|
+
class Setup < Legion::Extensions::Hooks::Base # rubocop:disable Legion/Extension/HookMissingRunnerClass
|
|
9
|
+
mount '/setup/callback'
|
|
10
|
+
|
|
11
|
+
def self.runner_class
|
|
12
|
+
'Legion::Extensions::Github::App::Runners::Manifest'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Github
|
|
6
|
+
module App
|
|
7
|
+
module Hooks
|
|
8
|
+
class Webhook < Legion::Extensions::Hooks::Base # rubocop:disable Legion/Extension/HookMissingRunnerClass
|
|
9
|
+
mount '/webhook'
|
|
10
|
+
|
|
11
|
+
def self.runner_class
|
|
12
|
+
'Legion::Extensions::Github::App::Runners::Webhooks'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|