9 Commits

Author SHA1 Message Date
Tom Alexander
ef195cd4df Update to alpine 3.20.
Some checks failed
semver Build semver has succeeded
build Build build has succeeded
format Build format has succeeded
rust-test Build rust-test has succeeded
clippy Build clippy has failed
2024-09-29 04:48:25 -04:00
Tom Alexander
a95339539b Use default namespace kubernetes client.
Some checks failed
semver Build semver has succeeded
format Build format has succeeded
rust-test Build rust-test has succeeded
clippy Build clippy has failed
build Build build has succeeded
2024-09-29 02:44:58 -04:00
Tom Alexander
07797b9906 Move the logic into the server.
Some checks failed
semver Build semver has succeeded
clippy Build clippy has failed
format Build format has succeeded
rust-test Build rust-test has succeeded
build Build build has succeeded
2024-09-29 01:27:00 -04:00
Tom Alexander
0548571b6b Add a pipeline to build the server image. 2024-09-29 00:36:51 -04:00
Tom Alexander
a2aca6d2f1 Add support for tags. 2024-09-29 00:13:56 -04:00
Tom Alexander
1efd7b1d73 Add a semver job to assign an automatically-incrementing version tag to commits to main.
Some checks failed
semver Build semver has succeeded
rust-test Build rust-test has succeeded
format Build format has succeeded
clippy Build clippy has failed
This automatically increments the patch (3rd) digit, so to update the major or minor version, manually push a tag.
2024-09-28 23:59:32 -04:00
Tom Alexander
b122e6ee99 Create a docker image for running the server. 2024-09-28 23:50:03 -04:00
Tom Alexander
c20927b726 Add Makefile for running CI jobs locally. 2024-09-28 23:20:35 -04:00
Tom Alexander
efe37f020a Generate names for pipeline runs.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
2024-09-28 22:50:35 -04:00
24 changed files with 883 additions and 118 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
**/.git
target/
docker/
.dockerignore

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target /target
TODO.org

View File

@@ -0,0 +1,222 @@
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: build
spec:
timeouts:
pipeline: "2h0m0s"
tasks: "1h0m0s"
finally: "0h30m0s"
taskRunTemplate:
serviceAccountName: build-bot
pipelineSpec:
params:
- name: image-name
description: The name for the built image
type: string
- name: path-to-image-context
description: The path to the build context
type: string
- name: path-to-dockerfile
description: The path to the Dockerfile
type: string
tasks:
- name: detect-tag
taskSpec:
metadata: {}
stepTemplate:
image: alpine:3.20
computeResources:
requests:
cpu: 10m
memory: 600Mi
workingDir: "$(workspaces.repo.path)"
results:
- name: tag
description: The tag to use for the docker container.
steps:
- image: alpine/git:v2.34.2
name: detect-tag-step
script: |
#!/usr/bin/env sh
set -euo pipefail
git fetch --tags
current_tag=$(git tag --points-at HEAD --list 'v*.*.*')
if [ -z "$current_tag" ]; then
echo "No tag at current commit"
exit 1
else
echo -n "${current_tag}" | tee $(results.tag.path)
fi
workspaces:
- name: repo
workspace: git-source
runAfter:
- fetch-repository
- name: report-pending
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/gitea-set-status/0.1/gitea-set-status.yaml
runAfter:
- fetch-repository
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has started"
- name: STATE
value: pending
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: fetch-repository
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/git-clone/0.9/git-clone.yaml
workspaces:
- name: output
workspace: git-source
params:
- name: url
value: $(params.REPO_URL)
- name: revision
value: $(params.PULL_BASE_SHA)
- name: deleteExisting
value: "true"
- name: build-image
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/kaniko/0.6//kaniko.yaml
params:
- name: IMAGE
value: "$(params.image-name):$(tasks.detect-tag.results.tag)"
- name: CONTEXT
value: $(params.path-to-image-context)
- name: DOCKERFILE
value: $(params.path-to-dockerfile)
- name: BUILDER_IMAGE
value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS
value:
- "--destination=$(params.image-name)" # Also write the :latest image
- --cache=true
- --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
- --use-new-run # Should result in a speed-up
- --reproducible # To remove timestamps so layer caching works.
- --snapshot-mode=redo
- --skip-unused-stages=true
- --registry-mirror=dockerhub.dockerhub.svc.cluster.local
workspaces:
- name: source
workspace: git-source
- name: dockerconfig
workspace: docker-credentials
runAfter:
- detect-tag
finally:
- name: report-success
when:
- input: "$(tasks.status)"
operator: in
values: ["Succeeded", "Completed"]
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/gitea-set-status/0.1/gitea-set-status.yaml
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has succeeded"
- name: STATE
value: success
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: report-failure
when:
- input: "$(tasks.status)"
operator: in
values: ["Failed"]
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/gitea-set-status/0.1/gitea-set-status.yaml
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has failed"
- name: STATE
value: failure
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
workspaces:
- name: git-source
- name: docker-credentials
workspaces:
- name: git-source
volumeClaimTemplate:
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
subPath: rust-source
- name: docker-credentials
secret:
secretName: harbor-plain
params:
- name: image-name
value: "harbor.fizz.buzz/private/webhook-bridge"
- name: path-to-image-context
value: .
- name: path-to-dockerfile
value: docker/webhook_bridge/Dockerfile

View File

@@ -111,9 +111,9 @@ spec:
- name: docker-image - name: docker-image
type: string type: string
description: Docker image to run. description: Docker image to run.
default: alpine:3.19 default: alpine:3.20
stepTemplate: stepTemplate:
image: alpine:3.19 image: alpine:3.20
computeResources: computeResources:
requests: requests:
cpu: 10m cpu: 10m
@@ -241,9 +241,9 @@ spec:
- name: docker-image - name: docker-image
type: string type: string
description: Docker image to run. description: Docker image to run.
default: alpine:3.19 default: alpine:3.20
stepTemplate: stepTemplate:
image: alpine:3.19 image: alpine:3.20
computeResources: computeResources:
requests: requests:
cpu: 10m cpu: 10m

View File

@@ -111,9 +111,9 @@ spec:
- name: docker-image - name: docker-image
type: string type: string
description: Docker image to run. description: Docker image to run.
default: alpine:3.19 default: alpine:3.20
stepTemplate: stepTemplate:
image: alpine:3.19 image: alpine:3.20
computeResources: computeResources:
requests: requests:
cpu: 10m cpu: 10m
@@ -220,9 +220,9 @@ spec:
- name: docker-image - name: docker-image
type: string type: string
description: Docker image to run. description: Docker image to run.
default: alpine:3.19 default: alpine:3.20
stepTemplate: stepTemplate:
image: alpine:3.19 image: alpine:3.20
computeResources: computeResources:
requests: requests:
cpu: 10m cpu: 10m

View File

@@ -111,9 +111,9 @@ spec:
- name: docker-image - name: docker-image
type: string type: string
description: Docker image to run. description: Docker image to run.
default: alpine:3.19 default: alpine:3.20
stepTemplate: stepTemplate:
image: alpine:3.19 image: alpine:3.20
computeResources: computeResources:
requests: requests:
cpu: 10m cpu: 10m
@@ -210,9 +210,9 @@ spec:
- name: docker-image - name: docker-image
type: string type: string
description: Docker image to run. description: Docker image to run.
default: alpine:3.19 default: alpine:3.20
stepTemplate: stepTemplate:
image: alpine:3.19 image: alpine:3.20
computeResources: computeResources:
requests: requests:
cpu: 10m cpu: 10m

View File

@@ -0,0 +1,189 @@
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: semver
spec:
timeouts:
pipeline: "2h0m0s"
tasks: "1h0m0s"
finally: "0h30m0s"
taskRunTemplate:
serviceAccountName: build-bot
pipelineSpec:
params:
- name: REPO_OWNER
description: Owner of the repo on gitea
type: string
- name: REPO_NAME
description: Name of the repo on gitea
type: string
- name: PULL_BASE_SHA
description: The commit sha
type: string
- name: JOB_NAME
description: The name of the job to report to gitea
type: string
tasks:
- name: calculate-tag
runAfter:
- report-pending
workspaces:
- name: source
workspace: git-source
taskSpec:
metadata: {}
stepTemplate:
image: alpine:3.20
computeResources:
requests:
cpu: 10m
memory: 600Mi
workingDir: /workspace/source
results:
- name: tag
description: The tag to use for the docker container
steps:
- image: alpine/git:2.43.0
name: calculate-tag
script: |
#!/usr/bin/env sh
set -euo pipefail
git config --global --add safe.directory $(workspaces.source.path)
git fetch --tags
current_tag=$(git tag --points-at HEAD --list 'v*.*.*')
if [ -z "$current_tag" ]; then
prev_tag=$(git tag --list 'v*.*.*' | sort -V -r | head -n 1)
if [ -n "$prev_tag" ]; then
last_bit=$(echo "$prev_tag" | cut -d '.' -f 3)
incremented=$((last_bit + 1))
prefix=$(echo "$prev_tag" | grep -oE 'v[0-9]*\.[0-9]*\.')
final_tag="${prefix}${incremented}"
else
final_tag="v0.0.1"
fi
echo -n "${final_tag}" | tee $(results.tag.path)
git tag "${final_tag}"
git push origin "${final_tag}"
else
echo -n "${current_tag}" | tee $(results.tag.path)
fi
- name: report-pending
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/gitea-set-status/0.1/gitea-set-status.yaml
runAfter:
- fetch-repository
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has started"
- name: STATE
value: pending
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: fetch-repository
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/git-clone/0.9/git-clone.yaml
workspaces:
- name: output
workspace: git-source
params:
- name: url
value: $(params.REPO_URL)
- name: revision
value: $(params.PULL_BASE_SHA)
- name: deleteExisting
value: "true"
finally:
- name: report-success
when:
- input: "$(tasks.status)"
operator: in
values: ["Succeeded", "Completed"]
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/gitea-set-status/0.1/gitea-set-status.yaml
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has succeeded"
- name: STATE
value: success
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: report-failure
when:
- input: "$(tasks.status)"
operator: in
values: ["Failed"]
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/gitea-set-status/0.1/gitea-set-status.yaml
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has failed"
- name: STATE
value: failure
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
workspaces:
- name: git-source
workspaces:
- name: git-source
volumeClaimTemplate:
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
subPath: source
params: []

View File

@@ -4,7 +4,7 @@ version = "0.0.1"
name = "rust-test" name = "rust-test"
source = "pipeline-rust-test.yaml" source = "pipeline-rust-test.yaml"
clone_uri = "git@code.fizz.buzz:talexander/webhook_bridge.git" clone_uri = "git@code.fizz.buzz:talexander/webhook_bridge.git"
branches = [ "^main$", "^master$" ] skip_branches = [ "^v[0-9]+\\.[0-9]+\\.[0-9]+$" ]
[[push]] [[push]]
name = "clippy" name = "clippy"
@@ -17,3 +17,15 @@ name = "format"
source = "pipeline-format.yaml" source = "pipeline-format.yaml"
clone_uri = "git@code.fizz.buzz:talexander/webhook_bridge.git" clone_uri = "git@code.fizz.buzz:talexander/webhook_bridge.git"
skip_branches = [ "^v[0-9]+\\.[0-9]+\\.[0-9]+$" ] skip_branches = [ "^v[0-9]+\\.[0-9]+\\.[0-9]+$" ]
[[push]]
name = "semver"
source = "pipeline-semver.yaml"
clone_uri = "git@code.fizz.buzz:talexander/webhook_bridge.git"
branches = [ "^main$", "^master$" ]
[[push]]
name = "build"
source = "pipeline-build.yaml"
clone_uri = "git@code.fizz.buzz:talexander/webhook_bridge.git"
branches = [ "^v[0-9]+\\.[0-9]+\\.[0-9]+$" ]

View File

@@ -7,7 +7,7 @@ description = "Trigger tekton jobs with gitea webhooks."
license = "0BSD" license = "0BSD"
repository = "https://code.fizz.buzz/talexander/webhook_bridge" repository = "https://code.fizz.buzz/talexander/webhook_bridge"
readme = "README.md" readme = "README.md"
keywords = ["gitea", "webhook"] keywords = ["tekton", "gitea", "webhook"]
categories = ["development-tools"] categories = ["development-tools"]
resolver = "2" resolver = "2"
include = [ include = [

35
Makefile Normal file
View File

@@ -0,0 +1,35 @@
SHELL := bash
.ONESHELL:
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
OS:=$(shell uname -s)
ifeq ($(origin .RECIPEPREFIX), undefined)
$(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)
endif
.RECIPEPREFIX = >
.PHONY: help
help:
> @grep -h "##" $(MAKEFILE_LIST) | grep -v grep | sed -E 's/^([^:]*): *## */\1: /'
.PHONY: test
test: ## Run the rust tests
> $(MAKE) -C docker/webhook_bridge_development build
> docker run --rm -i -t --mount type=tmpfs,destination=/tmp -v "$(shell readlink -f .):/source" --workdir=/source --env CARGO_TARGET_DIR=/target -v "webhook-bridge-cargo-registry:/usr/local/cargo/registry" -v "webhook-bridge-rust-target:/target" webhook-bridge-development cargo test
.PHONY: clippy
clippy: ## Run static analysis of the code.
> $(MAKE) -C docker/webhook_bridge_development build
> docker run --rm -i -t --mount type=tmpfs,destination=/tmp -v "$(shell readlink -f .):/source" --workdir=/source --env CARGO_TARGET_DIR=/target -v "webhook-bridge-cargo-registry:/usr/local/cargo/registry" -v "webhook-bridge-rust-target:/target" webhook-bridge-development cargo clippy --no-deps --all-targets --all-features -- -D warnings
.PHONY: format
format: ## Auto-format source files.
> $(MAKE) -C docker/webhook_bridge_development build
> docker run --rm -i -t --mount type=tmpfs,destination=/tmp -v "$(shell readlink -f .):/source" --workdir=/source --env CARGO_TARGET_DIR=/target -v "webhook-bridge-cargo-registry:/usr/local/cargo/registry" -v "webhook-bridge-rust-target:/target" webhook-bridge-development cargo fmt
.PHONY: clean
clean:
> $(MAKE) -C docker/webhook_bridge_development clean

View File

@@ -0,0 +1,15 @@
FROM rustlang/rust:nightly-alpine3.20 AS builder
RUN apk add --no-cache musl-dev pkgconfig libressl-dev
RUN mkdir /source
WORKDIR /source
COPY . .
# TODO: Add static build, which currently errors due to proc_macro. RUSTFLAGS="-C target-feature=+crt-static"
RUN CARGO_TARGET_DIR=/target cargo build --profile release-lto
FROM alpine:3.20 AS runner
COPY --from=builder /target/release-lto/webhook_bridge /usr/bin/
ENTRYPOINT ["/usr/bin/webhook_bridge"]

View File

@@ -0,0 +1,31 @@
SHELL := bash
.ONESHELL:
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
ifeq ($(origin .RECIPEPREFIX), undefined)
$(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)
endif
.RECIPEPREFIX = >
IMAGE_NAME:=webhook-bridge
TARGET :=
.PHONY: help
help:
> @grep -h "##" $(MAKEFILE_LIST) | grep -v grep | sed -E 's/^([^:]*): *## */\1: /'
.PHONY: build
build: ## Build the docker image.
> docker build --tag $(IMAGE_NAME) --target=$(TARGET) --file Dockerfile ../../
.PHONY: shell
shell: ## Launch an interactive shell inside the docker image with the source repository mounted at /source.
shell: build
> docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp $(IMAGE_NAME)
.PHONY: clean
clean:
> docker rmi $(IMAGE_NAME)

View File

@@ -1,6 +1,6 @@
FROM rustlang/rust:nightly-alpine3.19 AS builder FROM rustlang/rust:nightly-alpine3.20 AS builder
RUN apk add --no-cache musl-dev pkgconfig RUN apk add --no-cache musl-dev pkgconfig libressl3.8-libssl libressl-dev
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
RUN rustup component add rustfmt RUN rustup component add rustfmt
RUN rustup component add clippy RUN rustup component add clippy

View File

@@ -21,6 +21,7 @@ help:
build: ## Build the docker image. build: ## Build the docker image.
> docker build --tag $(IMAGE_NAME) --target=$(TARGET) --file Dockerfile . > docker build --tag $(IMAGE_NAME) --target=$(TARGET) --file Dockerfile .
> docker volume create webhook-bridge-cargo-registry > docker volume create webhook-bridge-cargo-registry
> docker volume create webhook-bridge-rust-target
.PHONY: shell .PHONY: shell
shell: ## Launch an interactive shell inside the docker image with the source repository mounted at /source. shell: ## Launch an interactive shell inside the docker image with the source repository mounted at /source.
@@ -31,3 +32,4 @@ shell: build
clean: clean:
> docker rmi $(IMAGE_NAME) > docker rmi $(IMAGE_NAME)
> docker volume rm webhook-bridge-cargo-registry > docker volume rm webhook-bridge-cargo-registry
> docker volume rm webhook-bridge-rust-target

View File

@@ -0,0 +1,165 @@
{
"ref": "refs/tags/v0.0.2",
"before": "0000000000000000000000000000000000000000",
"after": "84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"compare_url": "https://code.fizz.buzz/talexander/webhook_bridge/compare/0000000000000000000000000000000000000000...84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"commits": [],
"total_commits": 0,
"head_commit": {
"id": "84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"message": "Add a pipeline to build the server image.\n",
"url": "https://code.fizz.buzz/talexander/webhook_bridge/commit/84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"author": {
"name": "Tom Alexander",
"email": "tom@fizz.buzz",
"username": ""
},
"committer": {
"name": "Tom Alexander",
"email": "tom@fizz.buzz",
"username": ""
},
"verification": null,
"timestamp": "2024-09-29T00:19:22-04:00",
"added": [
".webhook_bridge/pipeline-build.yaml"
],
"removed": [],
"modified": [
".webhook_bridge/webhook_bridge.toml"
]
},
"repository": {
"id": 21,
"owner": {
"id": 1,
"login": "talexander",
"login_name": "",
"full_name": "",
"email": "gitea@local.domain",
"avatar_url": "https://code.fizz.buzz/avatars/9d402a89b5a0786f83c1b8c5486fc7ff3d083a54fe20e55c0a776a1932c30289",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2023-07-05T22:03:28Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "talexander"
},
"name": "webhook_bridge",
"full_name": "talexander/webhook_bridge",
"description": "A server that receives webhooks from gitea and fires off Tekton jobs in response.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"parent": null,
"mirror": false,
"size": 346,
"language": "",
"languages_url": "https://code.fizz.buzz/api/v1/repos/talexander/webhook_bridge/languages",
"html_url": "https://code.fizz.buzz/talexander/webhook_bridge",
"url": "https://code.fizz.buzz/api/v1/repos/talexander/webhook_bridge",
"link": "",
"ssh_url": "git@code.fizz.buzz:talexander/webhook_bridge.git",
"clone_url": "https://code.fizz.buzz/talexander/webhook_bridge.git",
"original_url": "",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2024-07-14T18:48:52Z",
"updated_at": "2024-09-29T04:25:36Z",
"archived_at": "1970-01-01T00:00:00Z",
"permissions": {
"admin": true,
"push": true,
"pull": true
},
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": true,
"has_projects": true,
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_squash_merge": true,
"allow_rebase_update": true,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "",
"internal": false,
"mirror_interval": "",
"mirror_updated": "0001-01-01T00:00:00Z",
"repo_transfer": null
},
"pusher": {
"id": 2,
"login": "build-bot",
"login_name": "",
"full_name": "",
"email": "build-bot@noreply.code.fizz.buzz",
"avatar_url": "https://secure.gravatar.com/avatar/e39ef2faba8a3dfb3dcb4d8275a532d4?d=identicon",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2023-07-09T04:25:44Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "private",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "build-bot"
},
"sender": {
"id": 2,
"login": "build-bot",
"login_name": "",
"full_name": "",
"email": "build-bot@noreply.code.fizz.buzz",
"avatar_url": "https://secure.gravatar.com/avatar/e39ef2faba8a3dfb3dcb4d8275a532d4?d=identicon",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2023-07-09T04:25:44Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "private",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "build-bot"
}
}

View File

@@ -1,13 +1,13 @@
{ {
"ref": "refs/heads/main", "ref": "refs/heads/main",
"before": "d5902e3e7f62cbd86e095437ccc33e945fcb0791", "before": "a2aca6d2f1c85b5d4bef1349230fdaef1683622d",
"after": "b8444344c4821e87a894cd195f8bec39cd501f68", "after": "84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"compare_url": "https://code.fizz.buzz/talexander/webhook_bridge/compare/d5902e3e7f62cbd86e095437ccc33e945fcb0791...b8444344c4821e87a894cd195f8bec39cd501f68", "compare_url": "https://code.fizz.buzz/talexander/webhook_bridge/compare/a2aca6d2f1c85b5d4bef1349230fdaef1683622d...84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"commits": [ "commits": [
{ {
"id": "b8444344c4821e87a894cd195f8bec39cd501f68", "id": "84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"message": "Update PipelineRun to tekton v1 from v1beta.\n", "message": "Add a pipeline to build the server image.\n",
"url": "https://code.fizz.buzz/talexander/webhook_bridge/commit/b8444344c4821e87a894cd195f8bec39cd501f68", "url": "https://code.fizz.buzz/talexander/webhook_bridge/commit/84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"author": { "author": {
"name": "Tom Alexander", "name": "Tom Alexander",
"email": "tom@fizz.buzz", "email": "tom@fizz.buzz",
@@ -19,21 +19,21 @@
"username": "" "username": ""
}, },
"verification": null, "verification": null,
"timestamp": "2024-09-28T20:33:35-04:00", "timestamp": "2024-09-29T00:19:22-04:00",
"added": [], "added": [
".webhook_bridge/pipeline-build.yaml"
],
"removed": [], "removed": [],
"modified": [ "modified": [
".webhook_bridge/pipeline-format.yaml", ".webhook_bridge/webhook_bridge.toml"
".webhook_bridge/pipeline-rust-clippy.yaml",
".webhook_bridge/pipeline-rust-test.yaml"
] ]
} }
], ],
"total_commits": 1, "total_commits": 1,
"head_commit": { "head_commit": {
"id": "b8444344c4821e87a894cd195f8bec39cd501f68", "id": "84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"message": "Update PipelineRun to tekton v1 from v1beta.\n", "message": "Add a pipeline to build the server image.\n",
"url": "https://code.fizz.buzz/talexander/webhook_bridge/commit/b8444344c4821e87a894cd195f8bec39cd501f68", "url": "https://code.fizz.buzz/talexander/webhook_bridge/commit/84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"author": { "author": {
"name": "Tom Alexander", "name": "Tom Alexander",
"email": "tom@fizz.buzz", "email": "tom@fizz.buzz",
@@ -45,13 +45,13 @@
"username": "" "username": ""
}, },
"verification": null, "verification": null,
"timestamp": "2024-09-28T20:33:35-04:00", "timestamp": "2024-09-29T00:19:22-04:00",
"added": [], "added": [
".webhook_bridge/pipeline-build.yaml"
],
"removed": [], "removed": [],
"modified": [ "modified": [
".webhook_bridge/pipeline-format.yaml", ".webhook_bridge/webhook_bridge.toml"
".webhook_bridge/pipeline-rust-clippy.yaml",
".webhook_bridge/pipeline-rust-test.yaml"
] ]
}, },
"repository": { "repository": {
@@ -88,7 +88,7 @@
"template": false, "template": false,
"parent": null, "parent": null,
"mirror": false, "mirror": false,
"size": 286, "size": 343,
"language": "", "language": "",
"languages_url": "https://code.fizz.buzz/api/v1/repos/talexander/webhook_bridge/languages", "languages_url": "https://code.fizz.buzz/api/v1/repos/talexander/webhook_bridge/languages",
"html_url": "https://code.fizz.buzz/talexander/webhook_bridge", "html_url": "https://code.fizz.buzz/talexander/webhook_bridge",
@@ -107,7 +107,7 @@
"default_branch": "main", "default_branch": "main",
"archived": false, "archived": false,
"created_at": "2024-07-14T18:48:52Z", "created_at": "2024-07-14T18:48:52Z",
"updated_at": "2024-09-28T23:43:53Z", "updated_at": "2024-09-29T04:14:47Z",
"archived_at": "1970-01-01T00:00:00Z", "archived_at": "1970-01-01T00:00:00Z",
"permissions": { "permissions": {
"admin": true, "admin": true,

View File

@@ -4,4 +4,4 @@ set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
curl -vv -H "Authorization: token $(cat /bridge/git/mrmanager/k8s/lighthouse/secrets/lighthouse/lighthouse/OAUTH_TOKEN)" 'https://code.fizz.buzz/api/v1/repos/talexander/organic/git/trees/841a348dd02f31ee8828f069b2a948712369069d?recursive=true&per_page=10&page=60' curl -vv -H "Authorization: token $(cat /bridge/git/mrmanager/k8s/webhook-bridge/secrets/webhook-bridge/webhook-bridge/OAUTH_TOKEN)" 'https://code.fizz.buzz/api/v1/repos/talexander/organic/git/trees/841a348dd02f31ee8828f069b2a948712369069d?recursive=true&per_page=10&page=60'

View File

@@ -4,4 +4,4 @@ set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
RUST_LOG=webhook_bridge=DEBUG WEBHOOK_BRIDGE_API_ROOT="https://code.fizz.buzz/api" WEBHOOK_BRIDGE_HMAC_SECRET=$(cat /bridge/git/mrmanager/k8s/lighthouse/secrets/lighthouse/lighthouse/HMAC_TOKEN) WEBHOOK_BRIDGE_OAUTH_TOKEN=$(cat /bridge/git/mrmanager/k8s/lighthouse/secrets/lighthouse/lighthouse/OAUTH_TOKEN) cargo run RUST_LOG=webhook_bridge=DEBUG WEBHOOK_BRIDGE_API_ROOT="https://code.fizz.buzz/api" WEBHOOK_BRIDGE_HMAC_SECRET=$(cat /bridge/git/mrmanager/k8s/webhook-bridge/secrets/webhook-bridge/webhook-bridge/HMAC_TOKEN) WEBHOOK_BRIDGE_OAUTH_TOKEN=$(cat /bridge/git/mrmanager/k8s/webhook-bridge/secrets/webhook-bridge/webhook-bridge/OAUTH_TOKEN) cargo run

View File

@@ -5,43 +5,43 @@ use crate::crd_pipeline_run::PipelineRun;
use crate::gitea_client::GiteaClient; use crate::gitea_client::GiteaClient;
use crate::gitea_client::Tree; use crate::gitea_client::Tree;
use crate::gitea_client::TreeFileReference; use crate::gitea_client::TreeFileReference;
use crate::in_repo_config::InRepoConfig; use crate::remote_config::RemoteConfig;
use regex::Regex; use regex::Regex;
use tracing::debug; use tracing::debug;
pub(crate) async fn discover_webhook_bridge_config( pub(crate) async fn discover_webhook_bridge_config(
gitea: &GiteaClient, gitea: &GiteaClient,
repo_tree: &Tree, repo_tree: &Tree,
) -> Result<InRepoConfig, Box<dyn std::error::Error>> { ) -> Result<RemoteConfig, Box<dyn std::error::Error>> {
let in_repo_config_reference = repo_tree let remote_config_reference = repo_tree
.files .files
.iter() .iter()
.filter(|file_reference| file_reference.path == ".webhook_bridge/webhook_bridge.toml") .filter(|file_reference| file_reference.path == ".webhook_bridge/webhook_bridge.toml")
.next() .next()
.ok_or("File not found in remote repo: .webhook_bridge/webhook_bridge.toml.")?; .ok_or("File not found in remote repo: .webhook_bridge/webhook_bridge.toml.")?;
let in_repo_config_contents = let remote_config_contents =
String::from_utf8(gitea.read_file(in_repo_config_reference).await?)?; String::from_utf8(gitea.read_file(remote_config_reference).await?)?;
let parsed_in_repo_config = InRepoConfig::from_str(in_repo_config_contents)?; let parsed_remote_config = RemoteConfig::from_str(remote_config_contents)?;
Ok(parsed_in_repo_config) Ok(parsed_remote_config)
} }
pub(crate) async fn discover_matching_push_triggers<RE: AsRef<str>>( pub(crate) async fn discover_matching_push_triggers<RE: AsRef<str>>(
gitea: &GiteaClient, gitea: &GiteaClient,
repo_tree: &Tree, repo_tree: &Tree,
git_ref: RE, git_ref: RE,
in_repo_config: &InRepoConfig, remote_config: &RemoteConfig,
) -> Result<Vec<PipelineTemplate>, Box<dyn std::error::Error>> { ) -> Result<Vec<PipelineTemplate>, Box<dyn std::error::Error>> {
let mut ret = Vec::new(); let mut ret = Vec::new();
let ref_to_branch_regex = Regex::new(r"refs/heads/(?P<branch>.+)")?; let ref_to_branch_regex = Regex::new(r"refs/(heads|tags)/(?P<branch>.+)")?;
let captures = ref_to_branch_regex let captures = ref_to_branch_regex
.captures(git_ref.as_ref()) .captures(git_ref.as_ref())
.ok_or("Could not find branch name.")?; .ok_or("Could not find branch name.")?;
let branch = &captures["branch"]; let branch = &captures["branch"];
debug!("Detected branch from push as {:?}", branch); debug!("Detected branch from push as {:?}", branch);
let push_triggers = in_repo_config.get_push_triggers_for_branch(branch)?; let push_triggers = remote_config.get_push_triggers_for_branch(branch)?;
for trigger in push_triggers { for trigger in push_triggers {
let path_to_source = normalize_path(Path::new(".webhook_bridge").join(&trigger.source)); let path_to_source = normalize_path(Path::new(".webhook_bridge").join(&trigger.source));
let pipeline_template = repo_tree let pipeline_template = repo_tree

View File

@@ -9,7 +9,7 @@ use serde_json::Value;
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub(crate) struct HookPush { pub(crate) struct HookPush {
#[serde(rename = "ref")] #[serde(rename = "ref")]
ref_field: String, pub(crate) ref_field: String,
before: String, before: String,
after: String, after: String,
compare_url: String, compare_url: String,
@@ -166,7 +166,7 @@ pub(crate) trait PipelineParamters {
impl PipelineParamters for HookPush { impl PipelineParamters for HookPush {
fn get_pull_base_ref(&self) -> Result<Cow<str>, Box<dyn std::error::Error>> { fn get_pull_base_ref(&self) -> Result<Cow<str>, Box<dyn std::error::Error>> {
let ref_to_branch_regex = Regex::new(r"refs/heads/(?P<branch>.+)")?; let ref_to_branch_regex = Regex::new(r"refs/(heads|tags)/(?P<branch>.+)")?;
let captures = ref_to_branch_regex let captures = ref_to_branch_regex
.captures(self.ref_field.as_str()) .captures(self.ref_field.as_str())
.ok_or("Could not find branch name.")?; .ok_or("Could not find branch name.")?;

View File

@@ -1,7 +1,12 @@
use std::borrow::Borrow;
use std::borrow::Cow;
use kube::api::PostParams; use kube::api::PostParams;
use kube::Api; use kube::Api;
use kube::CustomResourceExt; use kube::CustomResourceExt;
use regex::Regex;
use tracing::debug; use tracing::debug;
use tracing::info;
use crate::crd_pipeline_run::PipelineParam; use crate::crd_pipeline_run::PipelineParam;
use crate::crd_pipeline_run::PipelineRun; use crate::crd_pipeline_run::PipelineRun;
@@ -14,19 +19,24 @@ pub(crate) async fn run_pipelines(
pipelines: Vec<PipelineTemplate>, pipelines: Vec<PipelineTemplate>,
kubernetes_client: kube::Client, kubernetes_client: kube::Client,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let jobs: Api<PipelineRun> = Api::namespaced(kubernetes_client, "lighthouse"); // let jobs: Api<PipelineRun> = Api::namespaced(kubernetes_client, "webhook-bridge");
// let jobs: Api<PipelineRun> = Api::default_namespaced(kubernetes_client); let jobs: Api<PipelineRun> = Api::default_namespaced(kubernetes_client);
tracing::info!("Using crd: {}", serde_json::to_string(&PipelineRun::crd())?); tracing::debug!("Using crd: {}", serde_json::to_string(&PipelineRun::crd())?);
for mut pipeline in pipelines { for mut pipeline in pipelines {
debug!("Kicking off {}", pipeline.name); info!(
"Kicking off {} for repo {}/{}",
pipeline.name,
hook.get_repo_owner()?,
hook.get_repo_name()?,
);
if pipeline.pipeline.spec.params.is_none() { if pipeline.pipeline.spec.params.is_none() {
pipeline.pipeline.spec.params = Some(Vec::new()); pipeline.pipeline.spec.params = Some(Vec::new());
} }
if let Some(param_list) = pipeline.pipeline.spec.params.as_mut() { if let Some(param_list) = pipeline.pipeline.spec.params.as_mut() {
param_list.push(PipelineParam { param_list.push(PipelineParam {
name: Some("JOB_NAME".to_owned()), name: Some("JOB_NAME".to_owned()),
value: Some(serde_json::Value::String(pipeline.name)), value: Some(serde_json::Value::String(pipeline.name.clone())),
}); });
param_list.push(PipelineParam { param_list.push(PipelineParam {
name: Some("REPO_OWNER".to_owned()), name: Some("REPO_OWNER".to_owned()),
@@ -61,9 +71,55 @@ pub(crate) async fn run_pipelines(
)), )),
}); });
} }
if pipeline.pipeline.metadata.generate_name.is_none()
&& pipeline.pipeline.metadata.name.is_some()
{
std::mem::swap(
&mut pipeline.pipeline.metadata.generate_name,
&mut pipeline.pipeline.metadata.name,
);
if let Some(ref mut name) = pipeline.pipeline.metadata.generate_name {
let mut new_name_stub = sanitize_kubernetes_identifier(format!(
"{}-{}-{}",
name,
hook.get_repo_name()?,
hook.get_repo_owner()?
))?
.into_owned();
new_name_stub.truncate(63);
(*name) = new_name_stub + "-";
debug!("Using generate name: {}", name);
}
}
let pp = PostParams::default(); let pp = PostParams::default();
let created_run = jobs.create(&pp, &pipeline.pipeline).await?; let created_run = jobs.create(&pp, &pipeline.pipeline).await?;
info!("Created PipelineRun {:?}", created_run.metadata.name);
} }
Ok(()) Ok(())
} }
fn sanitize_kubernetes_identifier<'a, O: Into<Cow<'a, str>>>(
original: O,
) -> Result<Cow<'a, str>, Box<dyn std::error::Error>> {
let validate_identifier_regex =
Regex::new(r"[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*")?;
let original = original.into();
if !validate_identifier_regex.is_match(original.borrow()) {
return Ok(original);
}
let mut clean_identifier = String::with_capacity(original.len());
for c in original.chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '-' => {
clean_identifier.push(c);
}
_ => {
clean_identifier.push('-');
}
};
}
Ok(Cow::Owned(clean_identifier))
}

View File

@@ -1,5 +1,4 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use axum::http::StatusCode; use axum::http::StatusCode;
@@ -8,8 +7,6 @@ use axum::routing::get;
use axum::routing::post; use axum::routing::post;
use axum::Json; use axum::Json;
use axum::Router; use axum::Router;
use kube::api::PostParams;
use kube::Api;
use kube::Client; use kube::Client;
use serde::Serialize; use serde::Serialize;
use tokio::signal; use tokio::signal;
@@ -18,26 +15,25 @@ use tower_http::trace::TraceLayer;
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
use self::crd_pipeline_run::PipelineRun;
use self::discovery::discover_matching_push_triggers; use self::discovery::discover_matching_push_triggers;
use self::discovery::discover_webhook_bridge_config; use self::discovery::discover_webhook_bridge_config;
use self::gitea_client::GiteaClient; use self::gitea_client::GiteaClient;
use self::hook_push::HookPush; use self::hook_push::HookPush;
use self::in_repo_config::InRepoConfig; use self::hook_push::PipelineParamters;
use self::kubernetes::run_pipelines; use self::kubernetes::run_pipelines;
use self::webhook::handle_push;
use self::webhook::hook; use self::webhook::hook;
use self::webhook::verify_signature; use self::webhook::verify_signature;
use kube::CustomResourceExt;
mod crd_pipeline_run; mod crd_pipeline_run;
mod discovery; mod discovery;
mod gitea_client; mod gitea_client;
mod hook_push; mod hook_push;
mod in_repo_config;
mod kubernetes; mod kubernetes;
mod remote_config;
mod webhook; mod webhook;
const EXAMPLE_WEBHOOK_PAYLOAD: &'static str = include_str!("../example_webhook_payload.json"); const EXAMPLE_WEBHOOK_PAYLOAD: &'static str = include_str!("../example_tag_webhook_payload.json");
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -50,6 +46,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.with(tracing_subscriber::fmt::layer()) .with(tracing_subscriber::fmt::layer())
.init(); .init();
launch_server().await
}
async fn launch_server() -> Result<(), Box<dyn std::error::Error>> {
let kubernetes_client: Client = Client::try_default() let kubernetes_client: Client = Client::try_default()
.await .await
.expect("Set KUBECONFIG to a valid kubernetes config."); .expect("Set KUBECONFIG to a valid kubernetes config.");
@@ -58,41 +58,41 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?; let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?;
let gitea = GiteaClient::new(gitea_api_root, gitea_api_token); let gitea = GiteaClient::new(gitea_api_root, gitea_api_token);
let repo_tree = gitea let app = Router::new()
.get_tree( .route("/hook", post(hook))
"talexander", .layer(middleware::from_fn(verify_signature))
"webhook_bridge", .route("/health", get(health))
"b8444344c4821e87a894cd195f8bec39cd501f68", .layer((
) TraceLayer::new_for_http(),
.await?; // Add a timeout layer so graceful shutdown can't wait forever.
let in_repo_config = discover_webhook_bridge_config(&gitea, &repo_tree).await?; TimeoutLayer::new(Duration::from_secs(600)),
let pipelines = ))
discover_matching_push_triggers(&gitea, &repo_tree, "refs/heads/main", &in_repo_config) .with_state(AppState {
kubernetes_client,
gitea,
});
let listener = tokio::net::TcpListener::bind("0.0.0.0:9988").await?;
tracing::info!("listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?; .await?;
Ok(())
}
async fn local_trigger() -> Result<(), Box<dyn std::error::Error>> {
let kubernetes_client: Client = Client::try_default()
.await
.expect("Set KUBECONFIG to a valid kubernetes config.");
let gitea_api_root = std::env::var("WEBHOOK_BRIDGE_API_ROOT")?;
let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?;
let gitea = GiteaClient::new(gitea_api_root, gitea_api_token);
let webhook_payload: HookPush = serde_json::from_str(EXAMPLE_WEBHOOK_PAYLOAD)?; let webhook_payload: HookPush = serde_json::from_str(EXAMPLE_WEBHOOK_PAYLOAD)?;
run_pipelines(webhook_payload, pipelines, kubernetes_client).await?; handle_push(gitea, kubernetes_client, webhook_payload).await?;
// let app = Router::new()
// .route("/hook", post(hook))
// .layer(middleware::from_fn(verify_signature))
// .route("/health", get(health))
// .layer((
// TraceLayer::new_for_http(),
// // Add a timeout layer so graceful shutdown can't wait forever.
// TimeoutLayer::new(Duration::from_secs(600)),
// ))
// .with_state(AppState {
// kubernetes_client,
// gitea,
// });
// let listener = tokio::net::TcpListener::bind("0.0.0.0:9988").await?;
// tracing::info!("listening on {}", listener.local_addr().unwrap());
// axum::serve(listener, app)
// .with_graceful_shutdown(shutdown_signal())
// .await?;
Ok(()) Ok(())
} }

View File

@@ -5,7 +5,7 @@ use serde::Serialize;
/// The webhook_bridge.toml file that lives inside repos that have their CI triggered by webhook_bridge. /// The webhook_bridge.toml file that lives inside repos that have their CI triggered by webhook_bridge.
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub(crate) struct InRepoConfig { pub(crate) struct RemoteConfig {
pub(crate) version: String, pub(crate) version: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
@@ -29,16 +29,16 @@ pub(crate) struct TriggerPush {
pub(crate) skip_branches: Vec<String>, pub(crate) skip_branches: Vec<String>,
} }
impl InRepoConfig { impl RemoteConfig {
pub(crate) fn from_str<S: AsRef<str>>( pub(crate) fn from_str<S: AsRef<str>>(
contents: S, contents: S,
) -> Result<InRepoConfig, Box<dyn std::error::Error>> { ) -> Result<RemoteConfig, Box<dyn std::error::Error>> {
let parsed_in_repo_config: InRepoConfig = toml::from_str(contents.as_ref())?; let parsed_remote_config: RemoteConfig = toml::from_str(contents.as_ref())?;
assert!( assert!(
parsed_in_repo_config.version == "0.0.1", parsed_remote_config.version == "0.0.1",
"We only support version 0.0.1 currently." "We only support version 0.0.1 currently."
); );
Ok(parsed_in_repo_config) Ok(parsed_remote_config)
} }
pub(crate) fn get_push_triggers_for_branch<B: AsRef<str>>( pub(crate) fn get_push_triggers_for_branch<B: AsRef<str>>(

View File

@@ -22,7 +22,12 @@ use serde::Serialize;
use sha2::Sha256; use sha2::Sha256;
use tracing::debug; use tracing::debug;
use crate::discovery::discover_matching_push_triggers;
use crate::discovery::discover_webhook_bridge_config;
use crate::gitea_client::GiteaClient;
use crate::hook_push::HookPush; use crate::hook_push::HookPush;
use crate::hook_push::PipelineParamters;
use crate::kubernetes::run_pipelines;
use crate::AppState; use crate::AppState;
type HmacSha256 = Hmac<Sha256>; type HmacSha256 = Hmac<Sha256>;
@@ -34,13 +39,18 @@ pub(crate) async fn hook(
) -> (StatusCode, Json<HookResponse>) { ) -> (StatusCode, Json<HookResponse>) {
debug!("REQ: {:?}", payload); debug!("REQ: {:?}", payload);
match payload { match payload {
HookRequest::Push(_payload) => ( HookRequest::Push(webhook_payload) => {
handle_push(state.gitea, state.kubernetes_client, webhook_payload)
.await
.expect("Failed to handle push event.");
(
StatusCode::OK, StatusCode::OK,
Json(HookResponse { Json(HookResponse {
ok: true, ok: true,
message: None, message: None,
}), }),
), )
}
HookRequest::Unrecognized(payload) => ( HookRequest::Unrecognized(payload) => (
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
Json(HookResponse { Json(HookResponse {
@@ -153,3 +163,26 @@ fn hex_to_bytes(s: &str) -> Option<Vec<u8>> {
None None
} }
} }
pub(crate) async fn handle_push(
gitea: GiteaClient,
kubernetes_client: kube::Client,
webhook_payload: HookPush,
) -> Result<(), Box<dyn std::error::Error>> {
let repo_owner = webhook_payload.get_repo_owner()?;
let repo_name = webhook_payload.get_repo_name()?;
let pull_base_sha = webhook_payload.get_pull_base_sha()?;
let repo_tree = gitea.get_tree(repo_owner, repo_name, pull_base_sha).await?;
let remote_config = discover_webhook_bridge_config(&gitea, &repo_tree).await?;
let pipelines = discover_matching_push_triggers(
&gitea,
&repo_tree,
&webhook_payload.ref_field,
&remote_config,
)
.await?;
run_pipelines(webhook_payload, pipelines, kubernetes_client).await?;
Ok(())
}