Skip to content

Commit e74be9a

Browse files
Initial implementation
1 parent 1be2cf8 commit e74be9a

179 files changed

Lines changed: 20727 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 418 additions & 0 deletions
Large diffs are not rendered by default.

.env.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Example environment configuration for local development.
2+
# Copy this file to .env and adjust values to your setup.
3+
4+
# Local Postgres port (see docker-compose or local database configuration).
5+
DB_PORT=5432
6+
7+
# Base URL used for GitHub callback URLs (check-callback-url sent to workflow dispatch).
8+
# When running locally, set this to your machine's external IP so GitHub Actions can reach you.
9+
APP_BASE_URL=http://localhost:8080
10+
11+
# GitHub App credentials. Paste the entire PEM key in one line.
12+
# JwtSigner strips headers and newlines anyway.
13+
GITHUB_APP_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----<your-private-key>-----END PRIVATE KEY-----
14+
GITHUB_APP_ID=123456
15+
GITHUB_APP_INSTALLATION_ID=12345678

.github/dependabot.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 2
2+
3+
updates:
4+
- package-ecosystem: "gradle"
5+
directory: "/"
6+
schedule:
7+
interval: "weekly"
8+
9+
- package-ecosystem: "github-actions"
10+
directory: "/"
11+
schedule:
12+
interval: "weekly"

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: CI
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
push:
7+
branches: [ main ]
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
id-token: write
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
build:
20+
name: CI
21+
runs-on: ubuntu-latest
22+
outputs:
23+
released-version: ${{ steps.release.outputs.released-version }}
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- uses: actions/setup-java@v4
28+
with:
29+
distribution: temurin
30+
java-version: 21
31+
32+
- uses: gradle/actions/setup-gradle@v5
33+
with:
34+
dependency-graph: generate-and-submit
35+
36+
- run: ./gradlew build
37+
38+
- uses: mikepenz/action-junit-report@v5
39+
if: success() || failure()
40+
with:
41+
skip_success_summary: true
42+
comment: true
43+
report_paths: '**/test-results/*/*.xml'

.gitignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Intellij Idea project files
2+
.idea
3+
*.iml
4+
*.ipr
5+
*.iws
6+
7+
# gradle config
8+
.gradle
9+
10+
# project binaries
11+
build
12+
out
13+
classes
14+
15+
# sonar
16+
sonar-project.properties
17+
.sonar
18+
19+
# mac os x
20+
.DS_Store
21+
22+
# netbeans
23+
.nb-gradle
24+
25+
/config/*
26+
!/config/detekt
27+
28+
# provisioning auto-generated files
29+
provisioning/deployment.yml
30+
.kotlin/
31+
32+
# local env overrides
33+
.env

.gitkeep

Whitespace-only changes.

AGENTS.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# AGENTS.md
2+
3+
## Project Overview
4+
5+
**GitHub Workflow Orchestrator** is a service that orchestrates GitHub Actions workflows triggered by pull request events. It receives GitHub webhook events (`pull_request`, `check_run`), matches them against configurable workflow definitions stored in PostgreSQL, and dispatches GitHub Actions workflows via the GitHub API.
6+
7+
Key capabilities:
8+
- Configurable event filtering (JsonPath matchers, file path matchers, Dependabot matchers)
9+
- Concurrency control via a slot system to limit concurrent workflow runs
10+
- GitHub check run management (creates/updates check runs on PRs)
11+
- Comment callback system (OIDC-authenticated callbacks from dispatched workflows)
12+
- React frontend dashboard for managing workflow definitions (CRUD)
13+
14+
## Tech Stack
15+
16+
| Layer | Technology |
17+
|----------|----------------------------------------------------------------------------|
18+
| Backend | Kotlin 2.x, Spring Boot 3.x, JDK 21 |
19+
| Frontend | React 19, TypeScript 5.8, Vite 6, Mantine UI 7, TanStack Router + Query |
20+
| Database | PostgreSQL 16, Flyway migrations, Spring Data JDBC (`NamedParameterJdbcTemplate`) |
21+
| Scheduling | db-scheduler (persistent task queue in PostgreSQL) |
22+
| Build | Gradle 9.x (Kotlin DSL), version catalog (`libs.versions.toml`) |
23+
| Testing | Kotest (FunSpec), MockK, WireMock, Spring Boot Test (backend); Vitest (frontend) |
24+
| Auth | Auth0 java-jwt + jwks-rsa for GitHub App JWT authentication |
25+
26+
## Project Structure
27+
28+
```
29+
.
30+
├── backend/ # Kotlin/Spring Boot backend
31+
│ ├── build.gradle.kts
32+
│ └── src/
33+
│ ├── main/
34+
│ │ ├── kotlin/pl/allegro/tech/github/botorchestrator/
35+
│ │ │ ├── api/ # REST endpoints (webhooks, callbacks)
36+
│ │ │ ├── application/ # Event handlers (orchestration logic)
37+
│ │ │ ├── config/ # Spring configuration
38+
│ │ │ ├── domain/ # Core business logic & interfaces
39+
│ │ │ │ ├── filtering/ # PR matchers (JsonPath, FilePath, Dependabot)
40+
│ │ │ │ ├── workflows/ # Workflow definitions (CRUD, repository, config)
41+
│ │ │ │ ├── comment/ # Comment domain
42+
│ │ │ │ └── dependabot/ # Dependabot version parsing
43+
│ │ │ ├── infra/ # Infrastructure implementations
44+
│ │ │ │ ├── github/ # GitHub API client, auth (JWT, OIDC)
45+
│ │ │ │ ├── postgres/ # JDBC repositories, slots
46+
│ │ │ │ └── task/ # db-scheduler task definitions
47+
│ │ │ └── AppRunner.kt # Application entry point
48+
│ │ └── resources/
49+
│ │ ├── application.yml
50+
│ │ └── db/migration/ # Flyway SQL migrations (V001-V006)
51+
│ ├── test/ # Unit tests
52+
│ └── integration/ # Integration tests (WireMock, Spring Boot Test)
53+
├── frontend/ # React/TypeScript frontend
54+
│ ├── package.json
55+
│ ├── vite.config.ts
56+
│ └── src/
57+
│ ├── main.tsx # Entry point
58+
│ ├── App.tsx # Router + React Query setup
59+
│ ├── api.ts # Axios client with React Query
60+
│ ├── api-types.ts # Auto-generated from OpenAPI
61+
│ ├── routes/ # File-based routing (TanStack Router)
62+
│ └── components/ # UI components (WorkflowsTable, WorkflowForm, etc.)
63+
├── build-logic/ # Gradle convention plugins
64+
├── docs/ # Additional documentation
65+
├── compose.yml # Docker Compose (PostgreSQL for local dev)
66+
├── build.gradle.kts # Root build script
67+
├── settings.gradle.kts # Multi-module settings
68+
└── gradle/libs.versions.toml # Dependency version catalog
69+
```
70+
71+
## Architecture
72+
73+
The backend follows a **layered architecture**: `api` -> `application` -> `domain` -> `infra`.
74+
75+
**Request flow:** GitHub Webhook -> REST Endpoint (`api`) -> db-scheduler task (`infra/task`) -> Event Handler (`application`) -> Workflow Dispatcher (`domain`) -> GitHub API Client (`infra/github`)
76+
77+
Key patterns:
78+
- **Domain interfaces** (`GithubClient`, `AvailableSlots`, `WorkflowDefinitionRepository`) with infrastructure implementations (`RestGithubClient`, `PostgresAvailableSlots`, `JdbcWorkflowDefinitionRepository`)
79+
- **Strategy pattern** for PR filtering: `CompoundPullRequestMatcher` with `ANY`/`ALL` matching strategies and pluggable matchers
80+
- **Reliable processing**: Events are scheduled as db-scheduler one-time tasks with retry, not processed synchronously
81+
- **GitHub App auth**: JWT signing with token caching and TTL-based refresh
82+
83+
## Build & Run Commands
84+
85+
### Backend
86+
87+
```bash
88+
./gradlew run # Run the application (loads .env via build-logic plugin)
89+
./gradlew check # All tests (unit + integration)
90+
./gradlew test # Unit tests only
91+
./gradlew integrationTest # Integration tests only
92+
```
93+
94+
### Frontend
95+
96+
```bash
97+
npm run start # Dev server on port 3000 (proxies to backend)
98+
npm run build # TypeScript check + Vite build
99+
npm run test # Vitest
100+
npm run lint # ESLint
101+
npm run generate-types # Regenerate API types from OpenAPI spec
102+
```
103+
104+
### Infrastructure
105+
106+
```bash
107+
docker compose up # Start PostgreSQL 16 for local development
108+
```
109+
110+
## Code Conventions
111+
112+
- **Kotlin style**: `intellij_idea` ktlint code style, 4-space indent, 160-char line width (see `.editorconfig`)
113+
- **No trailing commas** in Kotlin
114+
- **No star imports** in Kotlin (`name_count_to_use_star_import = 2147483647`)
115+
- **Test style**: Kotest `FunSpec` with `shouldBe` matchers; specs named `*Spec.kt` (unit) and `*IntSpec.kt` (integration)
116+
- **Integration tests**: Located in `backend/src/integration/`, extend `BaseIntegrationSpec`, use WireMock for GitHub API mocking
117+
- **Frontend**: ESLint + Prettier enforced via Husky pre-commit hooks (lint-staged)
118+
- **API types**: Auto-generated from OpenAPI spec via `swagger-typescript-api` -- do not edit `api-types.ts` manually
119+
- **Frontend routing**: File-based with TanStack Router (routes in `frontend/src/routes/`)
120+
- **i18n**: Translations in `frontend/public/locales/{en-US,pl}/translation.json`
121+
122+
## Key Domain Concepts
123+
124+
- **Workflow Definition**: A configuration that maps GitHub events to a specific GitHub Actions workflow to dispatch. Stored in PostgreSQL, managed via REST API + frontend.
125+
- **Slot**: Concurrency control unit. Each dispatched workflow occupies a slot; `max-concurrent-runs` limits how many can run simultaneously.
126+
- **Concurrency Group**: Controls how GitHub Actions handles concurrent runs of the same workflow for the same PR (see `docs/concurrency-group.md`).
127+
- **Matchers/Filters**: Rules that determine whether an incoming PR event should trigger a workflow (JsonPath, file path regex, Dependabot).
128+
- **Check Callback**: URL passed to dispatched workflows so they can report status back to the PR check.
129+
- **Comment Callback**: URL passed to dispatched workflows so they can post comments on the PR (authenticated via GitHub OIDC).
130+
131+
## Environment Variables
132+
133+
See `.env.example` for required configuration:
134+
- `DB_PORT` -- PostgreSQL port
135+
- `APP_BASE_URL` -- Base URL for callback URLs
136+
- GitHub App credentials (app ID, private key, installation ID)
137+
138+
## Important Notes
139+
140+
- The project uses **Gradle composite builds** (`build-logic/` for convention plugins)
141+
- Database migrations are in `backend/src/main/resources/db/migration/` (Flyway, `V001` through `V006`)
142+
- Local seed data: `backend/src/main/resources/db/local/V0099__local.sql`
143+
- Docker Compose is configured for Spring Boot's Docker Compose support (`spring-boot-docker-compose` dependency)
144+
- The frontend build is integrated into Gradle via the `node-gradle` plugin

LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2026 Allegro.pl
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
GH Bot Orchestrator
2+
===================
3+
4+
A service that makes building custom GitHub bots easy!
5+
6+
## Usage
7+
8+
1. Install [GH Bot Orchestrator GitHub App](TODO) in your organization
9+
2. Deploy `gh-bot-orchestrator` to your infrastructure (see [deployment](#deployment))
10+
3. Register the bot via web UI to react on given GitHub events
11+
4. Create a workflow in a repository within your organization that looks like this:
12+
13+
```yaml
14+
on:
15+
workflow_dispatch:
16+
inputs:
17+
comment-callback-url:
18+
type: text
19+
# other inputs omitted for brevity
20+
21+
jobs:
22+
hello-world-bot:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Post PR comment
26+
uses: allegro/gh-bot-orchestrator/post-comment
27+
with:
28+
comment-callback-url: ${{ inputs.comment-callback-url }}
29+
message: Hello, I'm the bot!
30+
```
31+
32+
## Features
33+
34+
- a dedicated PR check is created for the bot execution
35+
- a bot can update the status of that check via provided `check-callback-url`
36+
- a bot can post PR comments via provided `comment-callback-url`
37+
- a bot can check out the original repository via provided readonly `github-clone-token`
38+
- a bot can do whatever possible via GitHub API (but a custom GitHub app is required for that)
39+
- a bot can access the original GitHub event
40+
- a bot can access list of files changed in a PR (it's not available in the event)
41+
- a bot can access Dependabot metadata (which dependency was bumped to which version)
42+
43+
## Use cases
44+
- run automatic code migrations for every dependabot PR bumping a specific dependency (see [allwrite](https://github.com/allegro/allwrite))
45+
- validate common config file correctness whenever it's modified
46+
- perform a security scan on the changed files
47+
- automatically add label when PR is approved (e.g. `_<username> : 👍`)
48+
49+
## Deployment
50+
51+
TODO

backend/build.gradle.kts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
plugins {
2+
alias(libs.plugins.kotlin.jvm)
3+
alias(libs.plugins.kotlin.spring)
4+
alias(libs.plugins.integration.test)
5+
alias(libs.plugins.test.logger)
6+
}
7+
8+
kotlin {
9+
jvmToolchain(21)
10+
}
11+
12+
dependencies {
13+
implementation(platform(libs.spring.boot.dependencies))
14+
implementation(libs.spring.boot.starter.web)
15+
16+
implementation(libs.kotlin.logging)
17+
implementation(libs.jackson.kotlin)
18+
implementation(libs.jackson.datatype)
19+
implementation(libs.auth0.java.jwt)
20+
implementation(libs.auth0.jwks.rsa)
21+
implementation(libs.java.semver)
22+
implementation(libs.json.path)
23+
implementation(libs.db.scheduler.spring.boot.starter)
24+
implementation(libs.postgresql)
25+
implementation(libs.spring.boot.starter.data.jdbc)
26+
implementation(libs.spring.boot.docker.compose)
27+
implementation(libs.springdoc.openapi.starter.webmvc.ui)
28+
29+
runtimeOnly(libs.flyway.core)
30+
runtimeOnly(libs.flyway.database.postgresql)
31+
32+
testImplementation(libs.bundles.kotest)
33+
testImplementation(libs.spring.boot.test)
34+
testImplementation(libs.flyway.core)
35+
testRuntimeOnly(libs.junit.platform.launcher)
36+
}
37+
38+
tasks {
39+
withType<Test> {
40+
useJUnitPlatform()
41+
}
42+
}

0 commit comments

Comments
 (0)