Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env.test
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
RUNBOAT_REPOS='[{"repo": "^oca/.*", "branch": "^15.0$", "builds": [{"image": "ghcr.io/oca/oca-ci/py3.8-odoo15.0:latest"}]}, {"repo": "^oca/.*", "branch": "^16.0$", "builds": [{"image": "ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest", "kubefiles_path": "/tmp"}]}]'
RUNBOAT_REPOS='[{"repo": "^oca/.*", "branch": "^15.0$", "builds": [{"image": "ghcr.io/oca/oca-ci/py3.8-odoo15.0:latest"}]}, {"repo": "^oca/.*", "branch": "^16.0$", "builds": [{"image": "ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest", "kubefiles_path": "/tmp"}]}, {"repo": "^kencove/.*", "branch": "^16.0.*$", "builds": [{"image": "ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest"}], "platform": "gitlab", "project_id": "19358266"}]'
RUNBOAT_API_ADMIN_USER="admin"
RUNBOAT_API_ADMIN_PASSWD="admin"
RUNBOAT_BUILD_NAMESPACE=runboat-builds
Expand All @@ -8,4 +8,7 @@ RUNBOAT_BUILD_SECRET_ENV='{"PGPASSWORD": "thepgpassword"}'
RUNBOAT_BUILD_TEMPLATE_VARS='{"storageClassName": "my-storage-class"}'
RUNBOAT_GITHUB_TOKEN=
RUNBOAT_GITHUB_WEBHOOK_SECRET=
RUNBOAT_GITLAB_TOKEN=
RUNBOAT_GITLAB_WEBHOOK_TOKEN=
RUNBOAT_GITLAB_URL=https://gitlab.com
RUNBOAT_LOG_CONFIG=log-config.yaml
93 changes: 93 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Build, Test & Push

on:
push:
branches: [main, "feat/**"]
pull_request:
branches: [main]

env:
REGISTRY: us-central1-docker.pkg.dev
IMAGE: us-central1-docker.pkg.dev/kencove-prod/kencove-docker-repo/runboat
CHART_REPO: oci://us-central1-docker.pkg.dev/kencove-prod/kencove-docker-repo

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install dependencies
run: pip install -e ".[test]"
- name: Run tests
run: pytest tests/ -v

build-image:
needs: test
runs-on: ubuntu-latest
Comment on lines +27 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Docker images are pushed on pull_request events too — likely unintended.

The build-image job has no if guard, so it runs on both push and pull_request triggers. This means PR builds will authenticate to GCP and push images to your production Artifact Registry, polluting it with throwaway PR images.

Add an if condition to restrict this job to push events, mirroring what push-chart already does:

Proposed fix
  build-image:
    needs: test
    runs-on: ubuntu-latest
+   if: github.event_name == 'push'
    permissions:
      contents: read
      id-token: write
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
build-image:
needs: test
runs-on: ubuntu-latest
build-image:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push'
permissions:
contents: read
id-token: write
🤖 Prompt for AI Agents
In @.github/workflows/build.yml around lines 27 - 29, The build-image job is
running for pull_request events and pushing images; restrict it to push events
by adding an if guard to the build-image job (the job named "build-image")
matching the same condition used by the push-chart job (e.g., github.event_name
== 'push' or the exact if expression used in push-chart) so the job only runs on
push events and not on pull_request events.

permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4

- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}

- uses: google-github-actions/setup-gcloud@v2

- name: Configure Docker for GCP Artifact Registry
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet

- name: Set image tags
id: tags
run: |
SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-12)
BRANCH=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9._-]/-/g')
echo "sha_tag=${SHA_SHORT}" >> "$GITHUB_OUTPUT"
echo "branch_tag=${BRANCH}" >> "$GITHUB_OUTPUT"

- name: Build Docker image
run: |
docker build \
-t ${{ env.IMAGE }}:${{ steps.tags.outputs.sha_tag }} \
-t ${{ env.IMAGE }}:${{ steps.tags.outputs.branch_tag }} \
${{ github.ref_name == 'main' && format('-t {0}:latest', env.IMAGE) || '' }} \
.

- name: Push Docker image
run: |
docker push ${{ env.IMAGE }}:${{ steps.tags.outputs.sha_tag }}
docker push ${{ env.IMAGE }}:${{ steps.tags.outputs.branch_tag }}
${{ github.ref_name == 'main' && format('docker push {0}:latest', env.IMAGE) || 'true' }}

push-chart:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref_name == 'main'
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4

- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}

- uses: google-github-actions/setup-gcloud@v2

- name: Configure Helm for GCP Artifact Registry
run: gcloud auth print-access-token | helm registry login us-central1-docker.pkg.dev -u oauth2accesstoken --password-stdin

- name: Package and push Helm chart
run: |
helm package chart/
helm push runboat-*.tgz ${{ env.CHART_REPO }}
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ ENV RUNBOAT_BUILD_TEMPLATE_VARS='{}'
ENV RUNBOAT_BUILD_DEFAULT_KUBEFILES_PATH=
ENV RUNBOAT_GITHUB_TOKEN=
ENV RUNBOAT_GITHUB_WEBHOOK_SECRET=
ENV RUNBOAT_GITLAB_TOKEN=
ENV RUNBOAT_GITLAB_WEBHOOK_TOKEN=
ENV RUNBOAT_GITLAB_URL=https://gitlab.com
ENV RUNBOAT_BASE_URL=https://runboat.example.com
ENV RUNBOAT_ADDITIONAL_FOOTER_HTML=''

Expand Down
158 changes: 147 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@

A simple Odoo runbot lookalike on kubernetes. Main goal is replacing the OCA runbot.

[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/sbidoul/runboat/main.svg)](https://results.pre-commit.ci/latest/github/sbidoul/runboat/main)
This fork adds **GitLab support** (webhooks, API, commit statuses) alongside the
existing GitHub support, plus a **Helm chart** for deployment.

## Principle of operation

This program is a Kubernetes operator that manages Odoo instances with pre-installed
addons. The addons come from commits on branches and pull requests in GitHub
repositories. A deployment of a given commit of a given branch or pull request of a
given repository is known as a build.
addons. The addons come from commits on branches and pull/merge requests in GitHub
or GitLab repositories. A deployment of a given commit of a given branch or
pull/merge request of a given repository is known as a build.

Runboat has the following main components:

- An in-memory database of deployed builds, with their current status.
- A REST API to list builds and trigger new deployments as well as start, stop, redeploy
or undeploy builds.
- A GitHub webhook to automatically trigger new builds on pushes to branches and pull
requests of supported repositories and branches (configured via regular expressions).
- **GitHub webhook** (`/webhooks/github`) to automatically trigger builds on pushes and
pull requests.
- **GitLab webhook** (`/webhooks/gitlab`) to automatically trigger builds on pushes and
merge requests.
- A controller that performs the following tasks:

- monitor deployments in a kubernetes namespaces to maintain the in-memory database;
- on new deployments, trigger an initialization job to check out the GitHub repo,
- monitor deployments in a kubernetes namespace to maintain the in-memory database;
- on new deployments, trigger an initialization job to check out the repo,
install dependencies, create the corresponding postgres database and install the
addons in it;
- initialization jobs are started concurrently up to a configured limit;
Expand Down Expand Up @@ -49,6 +52,40 @@ knowledge about *what* is deployed is in the
a specific contract to be managed by the runboat controller. This contract is described
in the [Kubernetes resources](#kubernetes-resources) section below.

## What's new in this fork

### GitLab support

GitLab support works alongside GitHub — both can be active simultaneously:

| Feature | GitHub | GitLab |
|---------|--------|--------|
| Webhook endpoint | `/webhooks/github` | `/webhooks/gitlab` |
| Webhook auth | HMAC-SHA256 signature | `X-Gitlab-Token` header |
| Push events | `push` | `Push Hook` |
| PR/MR events | `pull_request` | `Merge Request Hook` |
| Commit statuses | `POST /repos/{owner}/{repo}/statuses/{sha}` | `POST /api/v4/projects/{id}/statuses/{sha}` |
| Repo clone | GitHub tarball URL | GitLab archive API (supports private repos) |
| API trigger | `POST /api/v1/builds/trigger/pr` | `POST /api/v1/builds/trigger/mr` |

Configuration per repo (`RUNBOAT_REPOS`):
```json
[
{
"repo": "^myorg/myrepo$",
"branch": "^16\\.0$",
"platform": "gitlab",
"project_id": "12345678",
"builds": [{"image": "ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest"}]
}
]
```

### Helm chart

A Helm chart is included in `chart/` for deploying Runboat and its PostgreSQL
dependency to Kubernetes. See [Helm deployment](#helm-deployment) below.

## Requirements

For running the builds:
Expand All @@ -72,7 +109,104 @@ For running the controller (runboat itself):
The controller can be run outside the kubernetes cluster or deployed inside it, or even
in a different cluster.

## Deployment quickstart
## Helm deployment

The included Helm chart (`chart/`) deploys Runboat with all dependencies:

### Prerequisites

- Kubernetes 1.24+
- Helm 3.x
- nginx-ingress controller
- cert-manager (for TLS)
- CNPG operator (for managed PostgreSQL, or use external PG)
- Wildcard DNS pointing to your ingress load balancer

### Quick start

```bash
# 1. Create namespaces
kubectl create namespace runboat
kubectl create namespace runboat-builds

# 2. Create your values override
cat > my-values.yaml <<EOF
image:
repository: ghcr.io/your-org/runboat
tag: latest

ingress:
host: runboat.example.com

postgresql:
password: "$(openssl rand -hex 16)"
cnpg:
superuserPassword: "$(openssl rand -hex 16)"

config:
apiAdminPassword: "$(openssl rand -hex 16)"
buildDomain: runboat.example.com
baseUrl: https://runboat.example.com

github:
token: "ghp_your_token_here"
webhookSecret: "$(openssl rand -hex 20)"

gitlab:
token: "glpat-your_token_here"
webhookToken: "$(openssl rand -hex 16)"

repos:
- repo: "^oca/.*"
branch: "^16\\.0$"
platform: github
builds:
- image: "ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest"
EOF

# 3. Install
helm install runboat ./chart -n runboat -f my-values.yaml

# 4. Create wildcard cert for build ingresses (requires DNS-01 solver)
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: runboat-builds-wildcard
namespace: runboat-builds
spec:
secretName: runboat-builds-wildcard-tls
dnsNames:
- "*.runboat.example.com"
issuerRef:
kind: ClusterIssuer
name: letsencrypt-prod
EOF

# 5. Configure webhooks on your repos
# GitHub: Settings > Webhooks > Add webhook
# URL: https://runboat.example.com/webhooks/github
# Secret: <your webhook secret>
# Events: Pushes, Pull requests
#
# GitLab: Settings > Webhooks > Add new webhook
# URL: https://runboat.example.com/webhooks/gitlab
# Secret token: <your webhook token>
# Triggers: Push events, Merge request events
```

### Upgrading

```bash
helm upgrade runboat ./chart -n runboat -f my-values.yaml
```

### Configuration reference

See [`chart/values.yaml`](./chart/values.yaml) for all available settings with
inline documentation.

## Deployment quickstart (docker-compose)

A typical deployment looks like this.

Expand Down Expand Up @@ -134,7 +268,7 @@ actually deploy. It expects the following to hold true:
- `runboat/pr`: the pull request number if this build is for a pull request;
- `runboat/git-commit`: the commit sha.

- the home page of a running build is exposed at `http://{build_slug}.{build_domain}`.
- the home page of a running build is exposed at `https://{build_slug}.{build_domain}`.

During the lifecycle of a build, the controller does the following on the deployed
resources:
Expand Down Expand Up @@ -195,10 +329,12 @@ See environment variables examples in [Dockerfile](./Dockerfile),

## Credits

Authored by Stéphane Bidoul (@sbidoul) and
Authored by Stephane Bidoul (@sbidoul) and
[contributors](https://github.com/sbidoul/runboat/graphs/contributors) with support of
[ACSONE](https://acsone.eu).

GitLab support and Helm chart by [Kencove](https://kencove.com).

Contributions welcome.

Do not hesitate to reach out for help on how to get started.
Expand Down
6 changes: 6 additions & 0 deletions chart/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v2
name: runboat
description: Runboat - on-demand Odoo review environments on Kubernetes
type: application
version: 0.1.0
appVersion: "0.2"
Loading
Loading