29 Commits

Author SHA1 Message Date
Tom Alexander
f18c1fe421 Fix typo in pipelines.
Some checks failed
semver Build semver has succeeded
format Build format has succeeded
rust-test Build rust-test has succeeded
build Build build has succeeded
clippy Build clippy has failed
2024-09-30 00:03:10 -04:00
Tom Alexander
3c58d19a88 Add support for dockerfile targets to workflows.
Some checks failed
semver Build semver has succeeded
format Build format has succeeded
rust-test Build rust-test has succeeded
build Build build has succeeded
clippy Build clippy has failed
2024-09-29 23:23:22 -04:00
Tom Alexander
f07c0dc971 Rename pipeline-build to pipeline-build-semver to distinguish it from pipeline-build-hash used in poudboot. 2024-09-29 22:21:37 -04:00
Tom Alexander
fd7b22c5ce Remove cranelift.
Some checks failed
semver Build semver has succeeded
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
build Build build has succeeded
I should be configuring cranelift globally in my per-machine configs, not spreading my build preferences in the project's Cargo.toml.
2024-09-29 22:17:35 -04:00
Tom Alexander
1c082a5e24 Test: instantiate new clients for every request.
Some checks failed
semver Build semver has succeeded
format Build format has succeeded
build Build build has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
Trying to figure out why I am getting the below error occasionally in gitea:

Delivery: Post "https://webhookbridge.fizz.buzz/hook": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
2024-09-29 21:58:05 -04:00
Tom Alexander
9ed8905a5c Always return status code ok.
All checks were successful
semver Build semver has succeeded
format Build format has succeeded
clippy Build clippy has succeeded
build Build build has succeeded
rust-test Build rust-test has succeeded
2024-09-29 18:37:23 -04:00
Tom Alexander
8cb28459a0 Fix clippy lint. 2024-09-29 18:31:47 -04:00
Tom Alexander
753ad6dd05 Handle errors in push events.
Some checks failed
semver Build semver has succeeded
format Build format has succeeded
rust-test Build rust-test has failed
clippy Build clippy has failed
build Build build has succeeded
2024-09-29 18:24:50 -04:00
Tom Alexander
dd4c20f0a7 Remove log of secret.
All checks were successful
semver Build semver has succeeded
format Build format has succeeded
build Build build has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
2024-09-29 18:14:36 -04:00
Tom Alexander
c04b4e8da5 Fix bug that prevented actions from triggering.
All checks were successful
semver Build semver has succeeded
format Build format has succeeded
build Build build has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
2024-09-29 18:09:07 -04:00
Tom Alexander
69dd1ba156 Remove support for http2.
Nginx does not support http2 for upstream proxies because there is not much point for low-latency connections.
2024-09-29 18:00:34 -04:00
Tom Alexander
65c964b329 Fix clippy lint. 2024-09-29 17:42:08 -04:00
Tom Alexander
613026b326 Adding repo whitelist.
Some checks failed
semver Build semver has succeeded
format Build format has succeeded
clippy Build clippy has failed
build Build build has succeeded
rust-test Build rust-test has succeeded
2024-09-29 16:54:58 -04:00
Tom Alexander
cd56bb2fe1 Fix debug build in docker container by adding cranelift.
All checks were successful
semver Build semver has succeeded
format Build format has succeeded
clippy Build clippy has succeeded
build Build build has succeeded
rust-test Build rust-test has succeeded
2024-09-29 15:31:24 -04:00
Tom Alexander
4bcf8b9ddc Merge branch 'lint'
Some checks failed
semver Build semver has succeeded
format Build format has succeeded
rust-test Build rust-test has failed
clippy Build clippy has failed
build Build build has succeeded
2024-09-29 15:03:19 -04:00
Tom Alexander
14b38b7fcd Add rustfmt config. 2024-09-29 15:03:07 -04:00
Tom Alexander
0602f8472b Separate out to two binaries.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has failed
2024-09-29 14:59:39 -04:00
Tom Alexander
cdac8224c6 Fix clippy lints. 2024-09-29 14:08:05 -04:00
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
Tom Alexander
470031251c Remove prettier.
Some checks failed
rust-test Build rust-test has started
format Build format has started
clippy Build clippy has failed
2024-09-28 21:46:25 -04:00
Tom Alexander
ed1e1c08d0 Create PipelineRun in response to webhook triggers. 2024-09-28 21:40:00 -04:00
33 changed files with 1489 additions and 364 deletions

4
.dockerignore Normal file
View File

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

1
.gitignore vendored
View File

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

View File

@@ -0,0 +1,228 @@
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: target-name
description: The dockerfile target to build
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
- "--target=$(params.target-name)"
- --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: target-name
value: ""
- name: path-to-image-context
value: .
- name: path-to-dockerfile
value: docker/webhook_bridge/Dockerfile

View File

@@ -14,6 +14,9 @@ spec:
- name: image-name
description: The name for the built image
type: string
- name: target-name
description: The dockerfile target to build
type: string
- name: path-to-image-context
description: The path to the build context
type: string
@@ -89,6 +92,7 @@ spec:
value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS
value:
- "--target=$(params.target-name)"
- --cache=true
- --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
@@ -111,9 +115,9 @@ spec:
- name: docker-image
type: string
description: Docker image to run.
default: alpine:3.19
default: alpine:3.20
stepTemplate:
image: alpine:3.19
image: alpine:3.20
computeResources:
requests:
cpu: 10m
@@ -144,43 +148,6 @@ spec:
params:
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-prettier
taskSpec:
metadata: {}
params:
- name: docker-image
type: string
description: Docker image to run.
default: alpine:3.19
stepTemplate:
image: alpine:3.19
computeResources:
requests:
cpu: 10m
memory: 600Mi
workingDir: /workspace/source
workspaces:
- name: source
mountPath: /source
steps:
- name: run
image: $(params.docker-image)
workingDir: "$(workspaces.source.path)"
command: ["sh", "-c"]
args:
- |
prettier --write --no-error-on-unmatched-pattern "default_environment/**/*.js" "default_environment/**/*.css"
env:
- name: CARGO_TARGET_DIR
value: /target
workspaces:
- name: source
workspace: git-source
runAfter:
- run-cargo-fmt
params:
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: commit-changes
taskRef:
resolver: git
@@ -211,7 +178,7 @@ spec:
- name: source
workspace: git-source
runAfter:
- run-prettier
- run-cargo-fmt
finally:
- name: report-success
when:
@@ -278,9 +245,9 @@ spec:
- name: docker-image
type: string
description: Docker image to run.
default: alpine:3.19
default: alpine:3.20
stepTemplate:
image: alpine:3.19
image: alpine:3.20
computeResources:
requests:
cpu: 10m
@@ -329,7 +296,9 @@ spec:
secretName: harbor-plain
params:
- name: image-name
value: "harbor.fizz.buzz/private/webhook-bridge-development"
value: "harbor.fizz.buzz/private/webhook-bridge-development-format"
- name: target-name
value: ""
- name: path-to-image-context
value: docker/webhook_bridge_development/
- name: path-to-dockerfile

View File

@@ -14,6 +14,9 @@ spec:
- name: image-name
description: The name for the built image
type: string
- name: target-name
description: The dockerfile target to build
type: string
- name: path-to-image-context
description: The path to the build context
type: string
@@ -89,6 +92,7 @@ spec:
value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS
value:
- "--target=$(params.target-name)"
- --cache=true
- --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
@@ -111,9 +115,9 @@ spec:
- name: docker-image
type: string
description: Docker image to run.
default: alpine:3.19
default: alpine:3.20
stepTemplate:
image: alpine:3.19
image: alpine:3.20
computeResources:
requests:
cpu: 10m
@@ -220,9 +224,9 @@ spec:
- name: docker-image
type: string
description: Docker image to run.
default: alpine:3.19
default: alpine:3.20
stepTemplate:
image: alpine:3.19
image: alpine:3.20
computeResources:
requests:
cpu: 10m
@@ -271,7 +275,9 @@ spec:
secretName: harbor-plain
params:
- name: image-name
value: "harbor.fizz.buzz/private/webhook-bridge-development"
value: "harbor.fizz.buzz/private/webhook-bridge-development-clippy"
- name: target-name
value: ""
- name: path-to-image-context
value: docker/webhook_bridge_development/
- name: path-to-dockerfile

View File

@@ -14,6 +14,9 @@ spec:
- name: image-name
description: The name for the built image
type: string
- name: target-name
description: The dockerfile target to build
type: string
- name: path-to-image-context
description: The path to the build context
type: string
@@ -77,7 +80,7 @@ spec:
- name: revision
value: df36b3853a5657fd883015cdbf07ad6466918acf
- name: pathInRepo
value: task/kaniko/0.6//kaniko.yaml
value: task/kaniko/0.6/kaniko.yaml
params:
- name: IMAGE
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
@@ -89,6 +92,7 @@ spec:
value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS
value:
- "--target=$(params.target-name)"
- --cache=true
- --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
@@ -111,9 +115,9 @@ spec:
- name: docker-image
type: string
description: Docker image to run.
default: alpine:3.19
default: alpine:3.20
stepTemplate:
image: alpine:3.19
image: alpine:3.20
computeResources:
requests:
cpu: 10m
@@ -210,9 +214,9 @@ spec:
- name: docker-image
type: string
description: Docker image to run.
default: alpine:3.19
default: alpine:3.20
stepTemplate:
image: alpine:3.19
image: alpine:3.20
computeResources:
requests:
cpu: 10m
@@ -261,7 +265,9 @@ spec:
secretName: harbor-plain
params:
- name: image-name
value: "harbor.fizz.buzz/private/webhook-bridge-development"
value: "harbor.fizz.buzz/private/webhook-bridge-development-test"
- name: target-name
value: ""
- name: path-to-image-context
value: docker/webhook_bridge_development/
- name: path-to-dockerfile

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"
source = "pipeline-rust-test.yaml"
clone_uri = "git@code.fizz.buzz:talexander/webhook_bridge.git"
branches = [ "^main$", "^master$" ]
skip_branches = [ "^v[0-9]+\\.[0-9]+\\.[0-9]+$" ]
[[push]]
name = "clippy"
@@ -17,3 +17,15 @@ name = "format"
source = "pipeline-format.yaml"
clone_uri = "git@code.fizz.buzz:talexander/webhook_bridge.git"
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-semver.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"
repository = "https://code.fizz.buzz/talexander/webhook_bridge"
readme = "README.md"
keywords = ["gitea", "webhook"]
keywords = ["tekton", "gitea", "webhook"]
categories = ["development-tools"]
resolver = "2"
include = [
@@ -17,8 +17,26 @@ include = [
"Cargo.lock"
]
[lib]
name = "webhookbridge"
path = "src/lib.rs"
[[bin]]
name = "webhook_bridge"
path = "src/main.rs"
[[bin]]
# This bin exists for development purposes only. The real target of this crate is the webhook_bridge server binary.
name = "local_trigger"
path = "src/bin_local_trigger.rs"
required-features = ["local_trigger"]
[features]
default = ["local_trigger"]
local_trigger = []
[dependencies]
axum = { version = "0.7.5", default-features = false, features = ["tokio", "http1", "http2", "json"] }
axum = { version = "0.7.5", default-features = false, features = ["tokio", "http1", "json"] }
base64 = "0.22.1"
hmac = "0.12.1"
http-body-util = "0.1.2"

36
Makefile Normal file
View File

@@ -0,0 +1,36 @@
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
> rm -rf target

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 --bin webhook_bridge
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
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 rustup component add rustfmt
RUN rustup component add clippy

View File

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

View File

@@ -1,70 +0,0 @@
{
"apiVersion": "tekton.dev/v1",
"kind": "PipelineRun",
"metadata": {
"name": "minimal-test",
"namespace": "lighthouse"
},
"spec": {
"pipelineSpec": {
"tasks": [
{
"name": "echo-variable",
"taskSpec": {
"metadata": {},
"stepTemplate": {
"image": "alpine:3.18",
"computeResources": {
"requests": {
"cpu": "10m",
"memory": "600Mi"
}
}
},
"steps": [
{
"image": "alpine:3.18",
"script": "#!/usr/bin/env sh\necho \"The variable: $(params.LOREM)\"\n"
}
]
},
"params": [
{
"name": "LOREM",
"value": "$(tasks.set-variable.results.ipsum)"
}
]
},
{
"name": "set-variable",
"taskSpec": {
"metadata": {},
"stepTemplate": {
"image": "alpine:3.18",
"computeResources": {
"requests": {
"cpu": "10m",
"memory": "600Mi"
}
}
},
"results": [
{
"name": "ipsum"
}
],
"steps": [
{
"image": "alpine:3.18",
"script": "#!/usr/bin/env sh\necho -n \"dolar\" > \"$(results.ipsum.path)\"\n"
}
]
}
}
]
},
"timeouts": {
"pipeline": "240h0m0s"
}
}
}

View File

@@ -1,43 +0,0 @@
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: minimal-test
namespace: lighthouse
spec:
pipelineSpec:
tasks:
- name: echo-variable
taskSpec:
metadata: {}
stepTemplate:
image: alpine:3.18
computeResources:
requests:
cpu: 10m
memory: 600Mi
steps:
- image: alpine:3.18
script: |
#!/usr/bin/env sh
echo "The variable: $(params.LOREM)"
params:
- name: LOREM
value: $(tasks.set-variable.results.ipsum)
- name: set-variable
taskSpec:
metadata: {}
stepTemplate:
image: alpine:3.18
computeResources:
requests:
cpu: 10m
memory: 600Mi
results:
- name: ipsum
steps:
- image: alpine:3.18
script: |
#!/usr/bin/env sh
echo -n "dolar" > "$(results.ipsum.path)"
timeouts:
pipeline: 240h0m0s

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

@@ -0,0 +1,190 @@
{
"ref": "refs/heads/main",
"before": "a2aca6d2f1c85b5d4bef1349230fdaef1683622d",
"after": "84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"compare_url": "https://code.fizz.buzz/talexander/webhook_bridge/compare/a2aca6d2f1c85b5d4bef1349230fdaef1683622d...84fe1ec23ae242cb1bbccbc2ab999c3082f54d45",
"commits": [
{
"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"
]
}
],
"total_commits": 1,
"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": 343,
"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:14:47Z",
"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": 1,
"login": "talexander",
"login_name": "",
"full_name": "",
"email": "talexander@noreply.code.fizz.buzz",
"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"
},
"sender": {
"id": 1,
"login": "talexander",
"login_name": "",
"full_name": "",
"email": "talexander@noreply.code.fizz.buzz",
"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"
}
}

View File

@@ -4,4 +4,4 @@ set -euo pipefail
IFS=$'\n\t'
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'
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

14
rustfmt.toml Normal file
View File

@@ -0,0 +1,14 @@
imports_granularity = "Item"
group_imports = "StdExternalCrate"
# In rustfmt 2.0 I will want to adjust these settings.
#
# max_width controls the max length of a line before rustfmt gives up
# but that also scales the length of a bunch of other lines
# automaticaly due to width_heuristics. I want to find a way to enable
# rustfmt to work on longer lines when necessary without making my
# regular code too wide.
#
# max_width = 100
# error_on_line_overflow = true
# width_heuristics = "Off"

13
src/app_state.rs Normal file
View File

@@ -0,0 +1,13 @@
use std::collections::HashSet;
use std::sync::Arc;
use kube::Client;
use crate::gitea_client::GiteaClient;
#[derive(Clone)]
pub(crate) struct AppState {
pub(crate) kubernetes_client: Client,
pub(crate) gitea: GiteaClient,
pub(crate) allowed_repos: Arc<HashSet<String>>,
}

12
src/bin_local_trigger.rs Normal file
View File

@@ -0,0 +1,12 @@
#![forbid(unsafe_code)]
use webhookbridge::init_tracing;
use webhookbridge::local_trigger;
const EXAMPLE_WEBHOOK_PAYLOAD: &str = include_str!("../example_tag_webhook_payload.json");
#[tokio::main]
#[allow(clippy::needless_return)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_tracing().await?;
local_trigger(EXAMPLE_WEBHOOK_PAYLOAD).await
}

View File

@@ -14,11 +14,40 @@ use serde_json::Value;
plural = "pipelineruns"
)]
#[kube(namespaced)]
pub struct PipelineRunSpec {
#[serde(deny_unknown_fields)]
pub(crate) struct PipelineRunSpec {
/// Contents of the Pipeline
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pipelineSpec: Option<Value>,
#[serde(
rename = "pipelineSpec",
default,
skip_serializing_if = "Option::is_none"
)]
pub(crate) pipeline_spec: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeouts: Option<Value>,
pub(crate) timeouts: Option<Value>,
#[serde(
rename = "taskRunTemplate",
default,
skip_serializing_if = "Option::is_none"
)]
pub(crate) task_run_template: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) workspaces: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) params: Option<Vec<PipelineParam>>,
}
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(crate) struct PipelineParam {
/// Contents of the Pipeline
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) value: Option<Value>,
}

View File

@@ -1,54 +1,52 @@
use std::path::Path;
use std::path::PathBuf;
use regex::Regex;
use tracing::debug;
use crate::crd_pipeline_run::PipelineRun;
use crate::gitea_client::GiteaClient;
use crate::gitea_client::Tree;
use crate::gitea_client::TreeFileReference;
use crate::in_repo_config::InRepoConfig;
use regex::Regex;
use tracing::debug;
use crate::remote_config::RemoteConfig;
pub(crate) async fn discover_webhook_bridge_config(
gitea: &GiteaClient,
repo_tree: &Tree,
) -> Result<InRepoConfig, Box<dyn std::error::Error>> {
let in_repo_config_reference = repo_tree
) -> Result<RemoteConfig, Box<dyn std::error::Error>> {
let remote_config_reference = repo_tree
.files
.iter()
.filter(|file_reference| file_reference.path == ".webhook_bridge/webhook_bridge.toml")
.next()
.find(|file_reference| file_reference.path == ".webhook_bridge/webhook_bridge.toml")
.ok_or("File not found in remote repo: .webhook_bridge/webhook_bridge.toml.")?;
let in_repo_config_contents =
String::from_utf8(gitea.read_file(in_repo_config_reference).await?)?;
let parsed_in_repo_config = InRepoConfig::from_str(in_repo_config_contents)?;
let remote_config_contents =
String::from_utf8(gitea.read_file(remote_config_reference).await?)?;
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>>(
gitea: &GiteaClient,
repo_tree: &Tree,
git_ref: RE,
in_repo_config: &InRepoConfig,
remote_config: &RemoteConfig,
) -> Result<Vec<PipelineTemplate>, Box<dyn std::error::Error>> {
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
.captures(git_ref.as_ref())
.ok_or("Could not find branch name.")?;
let branch = &captures["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 {
let path_to_source = normalize_path(Path::new(".webhook_bridge").join(&trigger.source));
let pipeline_template = repo_tree
.files
.iter()
.filter(|file_reference| Path::new(&file_reference.path) == path_to_source.as_path())
.next()
.find(|file_reference| Path::new(&file_reference.path) == path_to_source.as_path())
.ok_or("Trigger source not found in remote repo.")?;
let pipeline_contents = String::from_utf8(gitea.read_file(pipeline_template).await?)?;
debug!("Pipeline template contents: {}", pipeline_contents);

View File

@@ -2,7 +2,9 @@ use std::error::Error;
#[derive(Debug, Clone)]
pub(crate) enum GiteaClientError {
#[allow(dead_code)]
Static(#[allow(dead_code)] &'static str),
#[allow(dead_code)]
String(#[allow(dead_code)] String),
NoTotalCountHeaderInResponse,
}

View File

@@ -1,4 +1,5 @@
use base64::{engine::general_purpose, Engine as _};
use base64::engine::general_purpose;
use base64::Engine as _;
use serde::Deserialize;
use tracing::debug;
@@ -37,7 +38,7 @@ impl GiteaClient {
owner = owner.as_ref(),
repo = repo.as_ref(),
commit = commit.as_ref(),
page = page.map(|num| format!("&page={}", num)).unwrap_or_else(|| String::new())
page = page.map(|num| format!("&page={}", num)).unwrap_or_default()
);
let response = self
.http_client
@@ -97,22 +98,39 @@ impl GiteaClient {
/// A single API response for GetTree containing only one page.
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct ResponseGetTree {
#[allow(dead_code)]
sha: String,
#[allow(dead_code)]
url: String,
tree: Vec<ResponseObjectReference>,
#[allow(dead_code)]
truncated: bool,
page: u64,
#[allow(dead_code)]
total_count: u64,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct ResponseObjectReference {
path: String,
#[allow(dead_code)]
mode: String,
#[allow(dead_code)]
#[serde(rename = "type")]
object_type: String,
#[allow(dead_code)]
size: u64,
#[allow(dead_code)]
sha: String,
url: String,
}
@@ -144,10 +162,17 @@ impl TreeFileReference {
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct ResponseReadFile {
content: String,
encoding: String,
#[allow(dead_code)]
url: String,
#[allow(dead_code)]
sha: String,
#[allow(dead_code)]
size: u64,
}

View File

@@ -1,23 +1,29 @@
use std::borrow::Cow;
use regex::Regex;
use serde::Deserialize;
use serde_json::Value;
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HookPush {
#[serde(rename = "ref")]
ref_field: String,
pub(crate) ref_field: String,
before: String,
after: String,
compare_url: String,
commits: Vec<HookCommit>,
total_commits: u64,
head_commit: HookCommit,
repository: HookRepository,
pub(crate) repository: HookRepository,
pusher: HookUser,
sender: HookUser,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HookUser {
id: u64,
login: String,
@@ -44,11 +50,12 @@ pub(crate) struct HookUser {
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HookRepository {
id: u64,
owner: HookUser,
name: String,
full_name: String,
pub(crate) full_name: String,
description: String,
empty: bool,
private: bool,
@@ -104,6 +111,7 @@ pub(crate) struct HookRepository {
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HookRepositoryPermissions {
admin: bool,
push: bool,
@@ -112,6 +120,7 @@ pub(crate) struct HookRepositoryPermissions {
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HookRepositoryInternalTracker {
enable_time_tracker: bool,
allow_only_contributors_to_track_time: bool,
@@ -120,6 +129,7 @@ pub(crate) struct HookRepositoryInternalTracker {
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HookCommit {
id: String,
message: String,
@@ -135,8 +145,48 @@ pub(crate) struct HookCommit {
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HookGitUser {
name: String,
email: String,
username: String,
}
pub(crate) trait PipelineParamters {
fn get_pull_base_ref(&self) -> Result<Cow<str>, Box<dyn std::error::Error>>;
fn get_pull_base_sha(&self) -> Result<Cow<str>, Box<dyn std::error::Error>>;
fn get_repo_url(&self) -> Result<Cow<str>, Box<dyn std::error::Error>>;
fn get_repo_name(&self) -> Result<Cow<str>, Box<dyn std::error::Error>>;
fn get_repo_owner(&self) -> Result<Cow<str>, Box<dyn std::error::Error>>;
}
impl PipelineParamters for HookPush {
fn get_pull_base_ref(&self) -> Result<Cow<str>, Box<dyn std::error::Error>> {
let ref_to_branch_regex = Regex::new(r"refs/(heads|tags)/(?P<branch>.+)")?;
let captures = ref_to_branch_regex
.captures(self.ref_field.as_str())
.ok_or("Could not find branch name.")?;
let branch = &captures["branch"];
Ok(Cow::Owned(branch.to_owned()))
}
fn get_pull_base_sha(&self) -> Result<Cow<str>, Box<dyn std::error::Error>> {
Ok(Cow::Borrowed(self.after.as_str()))
}
fn get_repo_url(&self) -> Result<Cow<str>, Box<dyn std::error::Error>> {
Ok(Cow::Borrowed(self.repository.clone_url.as_str()))
}
fn get_repo_name(&self) -> Result<Cow<str>, Box<dyn std::error::Error>> {
Ok(Cow::Borrowed(self.repository.name.as_str()))
}
fn get_repo_owner(&self) -> Result<Cow<str>, Box<dyn std::error::Error>> {
Ok(Cow::Borrowed(self.repository.owner.username.as_str()))
}
}

125
src/kubernetes.rs Normal file
View File

@@ -0,0 +1,125 @@
use std::borrow::Borrow;
use std::borrow::Cow;
use kube::api::PostParams;
use kube::Api;
use kube::CustomResourceExt;
use regex::Regex;
use tracing::debug;
use tracing::info;
use crate::crd_pipeline_run::PipelineParam;
use crate::crd_pipeline_run::PipelineRun;
use crate::discovery::PipelineTemplate;
use crate::hook_push::HookPush;
use crate::hook_push::PipelineParamters;
pub(crate) async fn run_pipelines(
hook: HookPush,
pipelines: Vec<PipelineTemplate>,
kubernetes_client: kube::Client,
) -> Result<(), Box<dyn std::error::Error>> {
// let jobs: Api<PipelineRun> = Api::namespaced(kubernetes_client, "webhook-bridge");
let jobs: Api<PipelineRun> = Api::default_namespaced(kubernetes_client);
tracing::debug!("Using crd: {}", serde_json::to_string(&PipelineRun::crd())?);
for mut pipeline in pipelines {
info!(
"Kicking off {} for repo {}/{}",
pipeline.name,
hook.get_repo_owner()?,
hook.get_repo_name()?,
);
if pipeline.pipeline.spec.params.is_none() {
pipeline.pipeline.spec.params = Some(Vec::new());
}
if let Some(param_list) = pipeline.pipeline.spec.params.as_mut() {
param_list.push(PipelineParam {
name: Some("JOB_NAME".to_owned()),
value: Some(serde_json::Value::String(pipeline.name.clone())),
});
param_list.push(PipelineParam {
name: Some("REPO_OWNER".to_owned()),
value: Some(serde_json::Value::String(
hook.get_repo_owner()?.into_owned(),
)),
});
param_list.push(PipelineParam {
name: Some("REPO_NAME".to_owned()),
value: Some(serde_json::Value::String(
hook.get_repo_name()?.into_owned(),
)),
});
let hook_repo_url = hook.get_repo_url()?;
param_list.push(PipelineParam {
name: Some("REPO_URL".to_owned()),
value: pipeline
.clone_uri
.map(serde_json::Value::String)
.or_else(|| Some(serde_json::Value::String(hook_repo_url.into_owned()))),
});
param_list.push(PipelineParam {
name: Some("PULL_BASE_SHA".to_owned()),
value: Some(serde_json::Value::String(
hook.get_pull_base_sha()?.into_owned(),
)),
});
param_list.push(PipelineParam {
name: Some("PULL_BASE_REF".to_owned()),
value: Some(serde_json::Value::String(
hook.get_pull_base_ref()?.into_owned(),
)),
});
}
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 created_run = jobs.create(&pp, &pipeline.pipeline).await?;
info!("Created PipelineRun {:?}", created_run.metadata.name);
}
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))
}

146
src/lib.rs Normal file
View File

@@ -0,0 +1,146 @@
#![forbid(unsafe_code)]
use std::collections::HashSet;
use std::sync::Arc;
use std::time::Duration;
use axum::http::StatusCode;
use axum::middleware;
use axum::routing::get;
use axum::routing::post;
use axum::Json;
use axum::Router;
use kube::Client;
use serde::Serialize;
use tokio::signal;
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use self::app_state::AppState;
use self::gitea_client::GiteaClient;
use self::hook_push::HookPush;
use self::webhook::handle_push;
use self::webhook::hook;
use self::webhook::verify_signature;
mod app_state;
mod crd_pipeline_run;
mod discovery;
mod gitea_client;
mod hook_push;
mod kubernetes;
mod remote_config;
mod webhook;
pub async fn init_tracing() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
"webhookbridge=info,webhook_bridge=info,local_trigger=info,tower_http=debug,axum::rejection=trace"
.into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
Ok(())
}
pub async fn launch_server() -> 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 allowed_repos = std::env::var("WEBHOOK_BRIDGE_REPO_WHITELIST")?;
let allowed_repos: HashSet<_> = allowed_repos
.split(",")
.filter(|s| !s.is_empty())
.map(str::to_owned)
.collect();
tracing::debug!("Using repo whitelist: {:?}", allowed_repos);
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,
allowed_repos: Arc::new(allowed_repos),
});
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(())
}
pub async fn local_trigger(payload: &str) -> 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 allowed_repos = std::env::var("WEBHOOK_BRIDGE_REPO_WHITELIST")
.ok()
.unwrap_or_default();
let allowed_repos: HashSet<_> = allowed_repos
.split(",")
.filter(|s| !s.is_empty())
.map(str::to_owned)
.collect();
tracing::debug!("Using repo whitelist: {:?}", allowed_repos);
let webhook_payload: HookPush = serde_json::from_str(payload)?;
handle_push(gitea, kubernetes_client, &allowed_repos, webhook_payload).await?;
Ok(())
}
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}
async fn health() -> (StatusCode, Json<HealthResponse>) {
(StatusCode::OK, Json(HealthResponse { ok: true }))
}
#[derive(Serialize)]
struct HealthResponse {
ok: bool,
}

View File

@@ -1,138 +1,10 @@
#![forbid(unsafe_code)]
use std::sync::Arc;
use std::time::Duration;
use axum::http::StatusCode;
use axum::middleware;
use axum::routing::get;
use axum::routing::post;
use axum::Json;
use axum::Router;
use kube::api::PostParams;
use kube::Api;
use kube::Client;
use serde::Serialize;
use tokio::signal;
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use self::crd_pipeline_run::PipelineRun;
use self::discovery::discover_matching_push_triggers;
use self::discovery::discover_webhook_bridge_config;
use self::gitea_client::GiteaClient;
use self::in_repo_config::InRepoConfig;
use self::webhook::hook;
use self::webhook::verify_signature;
use kube::CustomResourceExt;
mod crd_pipeline_run;
mod discovery;
mod gitea_client;
mod hook_push;
mod in_repo_config;
mod webhook;
const EXAMPLE_PIPELINE_RUN: &'static str = include_str!("../example_pipeline_run.json");
use webhookbridge::init_tracing;
use webhookbridge::launch_server;
#[tokio::main]
#[allow(clippy::needless_return)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
"webhook_bridge=info,tower_http=debug,axum::rejection=trace".into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
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 repo_tree = gitea
.get_tree(
"talexander",
"webhook_bridge",
"6d3b9e9db82d7857baa114d89efcb1bf470f448d",
)
.await?;
let in_repo_config = discover_webhook_bridge_config(&gitea, &repo_tree).await?;
let pipelines =
discover_matching_push_triggers(&gitea, &repo_tree, "refs/heads/main", &in_repo_config)
.await?;
// let jobs: Api<PipelineRun> = Api::namespaced(kubernetes_client, "lighthouse");
// let jobs: Api<PipelineRun> = Api::default_namespaced(kubernetes_client);
// tracing::info!("Using crd: {}", serde_json::to_string(&PipelineRun::crd())?);
// let test_run: PipelineRun = serde_json::from_str(EXAMPLE_PIPELINE_RUN)?;
// let pp = PostParams::default();
// let created_run = jobs.create(&pp, &test_run).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(())
}
#[derive(Clone)]
struct AppState {
kubernetes_client: Client,
gitea: GiteaClient,
}
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}
async fn health() -> (StatusCode, Json<HealthResponse>) {
(StatusCode::OK, Json(HealthResponse { ok: true }))
}
#[derive(Serialize)]
struct HealthResponse {
ok: bool,
init_tracing().await?;
launch_server().await
}

View File

@@ -4,7 +4,8 @@ use serde::Serialize;
/// The webhook_bridge.toml file that lives inside repos that have their CI triggered by webhook_bridge.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub(crate) struct InRepoConfig {
#[serde(deny_unknown_fields)]
pub(crate) struct RemoteConfig {
pub(crate) version: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
@@ -13,6 +14,7 @@ pub(crate) struct InRepoConfig {
/// A config for a job that is triggered by a push to a git repo.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub(crate) struct TriggerPush {
pub(crate) name: String,
pub(crate) source: String,
@@ -27,16 +29,16 @@ pub(crate) struct TriggerPush {
pub(crate) skip_branches: Vec<String>,
}
impl InRepoConfig {
impl RemoteConfig {
pub(crate) fn from_str<S: AsRef<str>>(
contents: S,
) -> Result<InRepoConfig, Box<dyn std::error::Error>> {
let parsed_in_repo_config: InRepoConfig = toml::from_str(contents.as_ref())?;
) -> Result<RemoteConfig, Box<dyn std::error::Error>> {
let parsed_remote_config: RemoteConfig = toml::from_str(contents.as_ref())?;
assert!(
parsed_in_repo_config.version == "0.0.1",
parsed_remote_config.version == "0.0.1",
"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>>(

View File

@@ -1,5 +1,6 @@
use std::borrow::Borrow;
use std::collections::HashSet;
use std::future::Future;
use std::sync::Arc;
use axum::async_trait;
use axum::body::Body;
@@ -14,7 +15,8 @@ use axum::response::IntoResponse;
use axum::response::Response;
use axum::Json;
use axum::RequestExt;
use base64::{engine::general_purpose, Engine as _};
use base64::engine::general_purpose;
use base64::Engine as _;
use hmac::Hmac;
use hmac::Mac;
use http_body_util::BodyExt;
@@ -22,8 +24,13 @@ use serde::Serialize;
use sha2::Sha256;
use tracing::debug;
use crate::app_state::AppState;
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::AppState;
use crate::hook_push::PipelineParamters;
use crate::kubernetes::run_pipelines;
type HmacSha256 = Hmac<Sha256>;
@@ -34,15 +41,55 @@ pub(crate) async fn hook(
) -> (StatusCode, Json<HookResponse>) {
debug!("REQ: {:?}", payload);
match payload {
HookRequest::Push(_payload) => (
HookRequest::Push(webhook_payload) => {
let kubernetes_client: kube::Client = kube::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_api_root, gitea_api_token) = match (gitea_api_root, gitea_api_token) {
(Ok(r), Ok(t)) => (r, t),
_ => {
return (
StatusCode::OK,
Json(HookResponse {
ok: true,
message: None,
}),
);
}
};
let gitea = GiteaClient::new(gitea_api_root, gitea_api_token);
let push_result = handle_push(
gitea,
kubernetes_client,
state.allowed_repos.borrow(),
webhook_payload,
)
.await;
match push_result {
Ok(_) => (
StatusCode::OK,
Json(HookResponse {
ok: true,
message: None,
}),
),
Err(_) => (
// StatusCode::INTERNAL_SERVER_ERROR,
StatusCode::OK,
Json(HookResponse {
ok: false,
message: Some("Failed to handle push event.".to_string()),
}),
),
}
}
HookRequest::Unrecognized(payload) => (
StatusCode::BAD_REQUEST,
// StatusCode::BAD_REQUEST,
StatusCode::OK,
Json(HookResponse {
ok: false,
message: Some(format!("unrecognized event type: {payload}")),
@@ -129,9 +176,9 @@ where
}
async fn check_hash(body: Bytes, secret: String, signature: Vec<u8>) -> Result<Bytes, Response> {
tracing::info!("Checking signature {:02x?}", signature.as_slice());
tracing::info!("Using secret {:?}", secret);
tracing::info!("and body {}", general_purpose::STANDARD.encode(&body));
tracing::debug!("Checking signature {:02x?}", signature.as_slice());
// tracing::info!("Using secret {:?}", secret);
tracing::debug!("and body {}", general_purpose::STANDARD.encode(&body));
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response())?;
mac.update(&body);
@@ -153,3 +200,34 @@ fn hex_to_bytes(s: &str) -> Option<Vec<u8>> {
None
}
}
pub(crate) async fn handle_push(
gitea: GiteaClient,
kubernetes_client: kube::Client,
allowed_repos: &HashSet<String>,
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()?;
if !allowed_repos.contains(&webhook_payload.repository.full_name) {
tracing::info!(
"{} is not an allowed repository.",
webhook_payload.repository.full_name
);
return Ok(());
}
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(())
}