Skip to content

Commit 442c40f

Browse files
committed
Add code for webhook dispatcher
A dispatcher is a new component to manage incoming webhooks from Github. It accept new requests and manage a queue based on cluster capacity. A capacity is defined as a number of running pipelines in certain namespace. This simple metrics will provide a buffer mechanism for time periods when we receive a bunk releases of operators. The mechanism keeps pending requests in the database queue and only trigger related pipeline in case of free capacity. The solution is made of: - Rest API - Postgres database - Dispatcher - Capacity manager JIRA: ISV-6108 Signed-off-by: Ales Raszka <[email protected]>
1 parent d8e2976 commit 442c40f

33 files changed

+2980
-70
lines changed

ansible/roles/integration_tests/tasks/check_bundle_existence_in_index_image.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
ansible.builtin.shell:
4646
cmd: |
4747
set -o pipefail &&
48-
export PATH="{{ temp_tools_dir.path }}:$PATH"
48+
export PATH="{{ integration_tests_temp_tools_dir.path }}:$PATH"
4949
while read image_url; do
5050
version=$(echo "$image_url" | sed 's/^.*:v//' )
5151
# Add stage to registry address

ansible/roles/integration_tests/tasks/check_pipeline_run.yml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,59 +3,59 @@
33
tags:
44
- verify-pipeline
55
block:
6-
- name: Find the run of {{ pipeline_name }}
6+
- name: Find the run of {{ pipeline_name }} #
77
kubernetes.core.k8s_info:
88
api_version: tekton.dev/v1
99
kind: PipelineRun
1010
namespace: "{{ oc_namespace }}"
1111
label_selectors:
1212
- "tekton.dev/pipeline={{ pipeline_name }}"
1313
- "suffix={{ suffix }}"
14-
register: pipeline_run
15-
until: pipeline_run.resources | length > 0
14+
register: integration_tests_pipeline_run
15+
until: integration_tests_pipeline_run.resources | length > 0
1616
retries: 10
1717
delay: 5
18-
failed_when: pipeline_run.resources | length == 0
18+
failed_when: integration_tests_pipeline_run.resources | length == 0
1919

2020
- name: Pipelinerun
2121
ansible.builtin.debug:
22-
var: pipeline_run.resources[0].metadata.name
22+
var: integration_tests_pipeline_run.resources[0].metadata.name
2323

2424
- name: "Wait for the run of {{ pipeline_name }}"
2525
kubernetes.core.k8s_info:
2626
api_version: tekton.dev/v1
2727
kind: PipelineRun
2828
namespace: "{{ oc_namespace }}"
29-
name: "{{ pipeline_run.resources[0].metadata.name }}"
30-
register: pipeline_run_wait
29+
name: "{{ integration_tests_pipeline_run.resources[0].metadata.name }}"
30+
register: integration_tests_pipeline_run_wait
3131
until: >
32-
pipeline_run_wait.resources[0] | community.general.json_query("contains(['False', 'True'], status.conditions[?type == 'Succeeded']|[-1].status)")
32+
integration_tests_pipeline_run_wait.resources[0] | community.general.json_query("contains(['False', 'True'], status.conditions[?type == 'Succeeded']|[-1].status)")
3333
retries: 90
3434
delay: 60
3535

3636
- name: "Get pipelinerun logs - {{ pipeline_name }}"
3737
ansible.builtin.shell: |
3838
./tkn pipeline logs \
3939
{{ pipeline_name }} \
40-
{{ pipeline_run.resources[0].metadata.name }} \
40+
{{ integration_tests_pipeline_run.resources[0].metadata.name }} \
4141
--namespace {{ oc_namespace }}
4242
args:
4343
executable: /bin/bash
44-
chdir: "{{ temp_tools_dir.path }}"
44+
chdir: "{{ integration_tests_temp_tools_dir.path }}"
4545
no_log: true
46-
register: pipeline_run_logs
46+
register: integration_tests_pipeline_run_logs
4747
changed_when: false
4848

4949
- name: "Print pipelinerun logs - {{ pipeline_name }}"
5050
ansible.builtin.debug:
51-
var: pipeline_run_logs.stdout_lines
51+
var: integration_tests_pipeline_run_logs.stdout_lines
5252

5353
- name: Verify successful run of {{ pipeline_name }}
5454
kubernetes.core.k8s_info:
5555
api_version: tekton.dev/v1
5656
kind: PipelineRun
5757
namespace: "{{ oc_namespace }}"
58-
name: "{{ pipeline_run.resources[0].metadata.name }}"
59-
register: pipeline_run
58+
name: "{{ integration_tests_pipeline_run.resources[0].metadata.name }}"
59+
register: integration_tests_pipeline_run
6060
failed_when: >
61-
pipeline_run.resources[0].status.conditions[-1].status != "True"
61+
integration_tests_pipeline_run.resources[0].status.conditions[-1].status != "True"

ansible/roles/integration_tests/tasks/clean.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
fi
1818
args:
1919
executable: /bin/bash
20-
chdir: "{{ git_temp_dir.path }}"
20+
chdir: "{{ integration_tests_git_temp_dir.path }}"
2121
changed_when: true

ansible/roles/integration_tests/tasks/clone.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
- name: Create temporary directory
77
ansible.builtin.tempfile:
88
state: directory
9-
register: git_temp_dir
9+
register: integration_tests_git_temp_dir
1010

1111
- name: Clone repository
1212
ansible.builtin.git:
1313
repo: "{{ integration_tests_git_repo_url }}"
14-
dest: "{{ git_temp_dir.path }}"
14+
dest: "{{ integration_tests_git_temp_dir.path }}"
1515
version: "{{ integration_tests_git_base_branch }}"
1616
refspec: "refs/heads/{{ integration_tests_src_operator_git_branch }}"

ansible/roles/integration_tests/tasks/open_pull_request.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
body: E2e test for {{ integration_tests_operator_package_name }} ({{ integration_tests_operator_bundle_version }})
1414
head: "{{ integration_tests_git_bundle_branch }}"
1515
base: "{{ integration_tests_git_upstream_branch }}"
16-
register: pr_response
16+
register: integration_tests_pr_response
1717
no_log: true
1818

1919
- name: Display PR response
2020
ansible.builtin.debug:
21-
var: pr_response
21+
var: integration_tests_pr_response

ansible/roles/integration_tests/tasks/test_data.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
- prepare-bundle
88
ansible.builtin.template:
99
src: ci.yaml.j2
10-
dest: "{{ git_temp_dir.path }}/ci.yaml"
10+
dest: "{{ integration_tests_git_temp_dir.path }}/ci.yaml"
1111
mode: "0644"
1212
changed_when: true
1313

@@ -35,7 +35,7 @@
3535
git push origin "$UPSTREAM_BRANCH"
3636
args:
3737
executable: /bin/bash
38-
chdir: "{{ git_temp_dir.path }}"
38+
chdir: "{{ integration_tests_git_temp_dir.path }}"
3939
changed_when: true
4040

4141
- name: Generate and push Operator Bundle
@@ -102,7 +102,7 @@
102102
git push origin "$BUNDLE_BRANCH"
103103
args:
104104
executable: /bin/bash
105-
chdir: "{{ git_temp_dir.path }}"
105+
chdir: "{{ integration_tests_git_temp_dir.path }}"
106106
changed_when: true
107107

108108
- name: Generate and push Catalog to a PR branch
@@ -153,6 +153,6 @@
153153
git push origin "$PR_BRANCH"
154154
args:
155155
executable: /bin/bash
156-
chdir: "{{ git_temp_dir.path }}"
156+
chdir: "{{ integration_tests_git_temp_dir.path }}"
157157
changed_when: true
158158
when: integration_tests_fbc_catalog

ansible/roles/integration_tests/tasks/tools.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,31 @@
66
- name: Create temporary directory
77
ansible.builtin.tempfile:
88
state: directory
9-
register: temp_tools_dir
9+
register: integration_tests_temp_tools_dir
1010

1111
- name: Download and extract the tkn binary
1212
ansible.builtin.unarchive:
1313
src: https://mirror.openshift.com/pub/openshift-v4/clients/pipelines/1.18.0/tkn-linux-amd64.tar.gz
14-
dest: "{{ temp_tools_dir.path }}"
14+
dest: "{{ integration_tests_temp_tools_dir.path }}"
1515
remote_src: true
1616
include:
1717
- tkn
1818

1919
- name: Download and extract the opm binary
2020
ansible.builtin.unarchive:
2121
src: https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/latest-4.18/opm-linux.tar.gz
22-
dest: "{{ temp_tools_dir.path }}"
22+
dest: "{{ integration_tests_temp_tools_dir.path }}"
2323
remote_src: true
2424
include:
2525
- opm-rhel8
2626

2727
- name: Rename the opm binary
2828
ansible.builtin.copy:
29-
src: "{{ temp_tools_dir.path }}/opm-rhel8"
30-
dest: "{{ temp_tools_dir.path }}/opm"
29+
src: "{{ integration_tests_temp_tools_dir.path }}/opm-rhel8"
30+
dest: "{{ integration_tests_temp_tools_dir.path }}/opm"
3131
mode: '0755'
3232

3333
- name: Delete the original file
3434
ansible.builtin.file:
35-
path: "{{ temp_tools_dir.path }}/opm-rhel8"
35+
path: "{{ integration_tests_temp_tools_dir.path }}/opm-rhel8"
3636
state: absent

ansible/roles/operator-pipeline/tasks/index-img-bootstrap-signing.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
name: tekton-pipeline-executor
102102
subjects:
103103
- kind: ServiceAccount
104-
name: "{{ item}}"
104+
name: "{{ item }}"
105105
namespace: "{{ oc_index_bootstrap_namespace }}"
106106
with_items: "{{ index_img_bootstrap_service_accounts }}"
107107

docker-compose.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
services:
3+
db:
4+
image: registry.redhat.io/rhel10/postgresql-16@sha256:8515941a42d6bc5d59b753839174b3c0e839315d686ca2fdd6fa65e1cccf3390
5+
container_name: webhook-dispatcher-db
6+
restart: unless-stopped
7+
environment:
8+
POSTGRESQL_USER: user
9+
POSTGRESQL_PASSWORD: password
10+
POSTGRESQL_DATABASE: webhook_dispatcher
11+
ports:
12+
- "5432:5432"
13+
volumes:
14+
- pgdata:/var/lib/postgresql/data
15+
healthcheck:
16+
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
17+
interval: 10s
18+
timeout: 5s
19+
retries: 5
20+
21+
volumes:
22+
pgdata:

docs/webhook-dispatecher.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Webhook Dispatcher
2+
3+
The Webhook Dispatcher is a microservice that receives GitHub webhooks and processes them for operator pipelines.
4+
It validates webhooks, stores events in a database, and triggers Tekton pipelines for operator pipelines with
5+
a capacity management system.
6+
7+
## Overview
8+
9+
The Webhook Dispatcher serves as the entry point for GitHub events in the operator pipelines.
10+
When developers submit operators via pull requests, the dispatcher:
11+
12+
1. **Receives and validates** GitHub webhooks with signature verification
13+
2. **Stores webhook events** in a PostgreSQL database
14+
3. **Manages pipeline capacity** to prevent resource exhaustion
15+
4. **Triggers Tekton pipelines** for operator validation
16+
17+
## Architecture
18+
19+
```
20+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
21+
│ GitHub │ │ Webhook │ │ PostgreSQL │
22+
│ Webhooks │───▶│ Dispatcher │───▶│ Database │
23+
└─────────────────┘ └──────────────────┘ └─────────────────┘
24+
25+
26+
┌──────────────────┐
27+
│ Tekton │
28+
│ Pipelines │
29+
└──────────────────┘
30+
```
31+
32+
## Features
33+
34+
- **Secure webhook processing** with HMAC-SHA256 signature verification
35+
- **Multi-repository support** with configurable processing rules
36+
- **Pipeline capacity management** to prevent resource overload
37+
- **Background event processing** for improved performance
38+
- **Health monitoring** and status tracking
39+
40+
41+
## Installation
42+
43+
### Prerequisites
44+
- Python 3.11+
45+
- PostgreSQL 12+
46+
- Kubernetes/OpenShift cluster access
47+
48+
### Setup
49+
50+
1. Install dependencies:
51+
```bash
52+
pdm install --no-dev
53+
```
54+
55+
2. Create database:
56+
```bash
57+
docker compose up -d
58+
```
59+
60+
3. Set environment variables:
61+
```bash
62+
export DATABASE_URL="postgresql://webhook_user:secure_password@localhost:5432/webhook_dispatcher"
63+
export GITHUB_WEBHOOK_SECRET="your_github_webhook_secret"
64+
```
65+
66+
## Configuration
67+
68+
Create a `dispatcher_config.yaml` file:
69+
70+
```yaml
71+
---
72+
dispatcher:
73+
items:
74+
- name: "Community Operators"
75+
events:
76+
- opened
77+
- synchronize
78+
- ready_for_review
79+
full_repository_name: "redhat-openshift-ecosystem/community-operators"
80+
capacity:
81+
type: ocp_tekton
82+
pipeline_name: "operator-hosted-pipeline"
83+
max_capacity: 5
84+
namespace: "operator-pipeline-prod"
85+
# Tekton pipeline listener URL
86+
callback_url: "https://tekton-dashboard.example.com/api/v1/trigger"
87+
88+
security:
89+
github_webhook_secret: "${GITHUB_WEBHOOK_SECRET}"
90+
verify_signatures: true
91+
allowed_github_events:
92+
- pull_request
93+
```
94+
95+
### Configuration Parameters
96+
97+
- **name**: Configuration identifier
98+
- **events**: GitHub PR events to process
99+
- **full_repository_name**: Repository in `owner/repo` format
100+
- **capacity**: Pipeline capacity settings
101+
- **callback_url**: Tekton pipeline trigger URL
102+
- **github_webhook_secret**: Webhook signature verification secret
103+
104+
## Usage
105+
106+
### Running the Service
107+
108+
Production mode:
109+
```bash
110+
export CONFIG_FILE="/path/to/dispatcher_config.yaml"
111+
python -m operatorcert.webhook_dispatcher.main
112+
```
113+
114+
### GitHub Webhook Setup
115+
116+
1. Go to repository **Settings** → **Webhooks**
117+
2. Add webhook with:
118+
- **URL**: `https://your-domain.com/api/v1/webhooks/github-pipeline`
119+
- **Content type**: `application/json`
120+
- **Secret**: Your webhook secret
121+
- **Events**: Pull requests
122+
123+
## API Endpoints
124+
125+
- `POST /api/v1/webhooks/github-pipeline` - Receive GitHub webhooks
126+
- `GET /api/v1/status/ping` - Health check
127+
- `GET /api/v1/status/db` - Database health
128+
- `GET /api/v1/events/status` - Event status with pagination
129+
130+
## Security
131+
132+
- **HMAC-SHA256 signature verification** for all webhook requests
133+
- **GitHub User-Agent validation** to prevent spoofing
134+
- **Event type filtering** based on configuration
135+
- **TLS encryption** required for production deployments
136+
137+
## Monitoring
138+
139+
### Health Checks
140+
```bash
141+
curl http://localhost:5000/api/v1/status/ping
142+
curl http://localhost:5000/api/v1/status/db
143+
```
144+
145+
### Event Status
146+
```bash
147+
curl "http://localhost:5000/api/v1/events/status?page_size=20"
148+
```
149+
150+
## Troubleshooting
151+
152+
### Common Issues
153+
154+
**Signature verification failures**:
155+
- Verify `GITHUB_WEBHOOK_SECRET` matches GitHub configuration
156+
- Ensure webhook uses `application/json` content type
157+
158+
**Database connection errors**:
159+
- Check `DATABASE_URL` format
160+
- Verify PostgreSQL is running and accessible
161+
162+
**Event processing delays**:
163+
- Check dispatcher thread logs
164+
- Monitor pipeline capacity usage
165+
- Review database performance
166+
167+
### Debug Mode
168+
```bash
169+
export LOG_LEVEL=DEBUG
170+
python -m operatorcert.webhook_dispatcher.main --verbose
171+
```
172+
173+
For support, refer to the [operator-pipelines repository](https://github.com/redhat-openshift-ecosystem/operator-pipelines).

0 commit comments

Comments
 (0)