Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
853d2ba803 |
@@ -30,9 +30,9 @@
|
||||
# Triggering:
|
||||
# - cron : a cron NAMED exactly "alert-monitor" (name filter, same
|
||||
# mechanism as update-audit.yml):
|
||||
# woodpecker repo cron add --repo konairius/homelab \
|
||||
# woodpecker repo cron add --repo k.renner/homelab \
|
||||
# --name alert-monitor --branch master --schedule '*/15 * * * *'
|
||||
# - deploy : woodpecker-cli pipeline deploy konairius/homelab <n> alert-monitor
|
||||
# - deploy : woodpecker-cli pipeline deploy k.renner/homelab <n> alert-monitor
|
||||
# where <n> is an existing pipeline to promote.
|
||||
when:
|
||||
- event: cron
|
||||
@@ -130,7 +130,7 @@ steps:
|
||||
| select([.receivers[]?.name] | (length == 0) or any(. != "null"))]' \
|
||||
.ci-alert-monitor/alerts-raw.json > .ci-alert-monitor/alerts-active.json
|
||||
echo "Active alerts (after heartbeat filter): $$(jq length .ci-alert-monitor/alerts-active.json)"
|
||||
API="https://gitea.renner.uno/api/v1/repos/konairius/homelab"
|
||||
API="https://gitea.renner.uno/api/v1/repos/k.renner/homelab"
|
||||
curl -sfL "$$API/issues?state=open&type=issues&limit=100" > .ci-alert-monitor/open-issues.json
|
||||
TITLES=$$(jq -c '[.[].title]' .ci-alert-monitor/open-issues.json)
|
||||
NOISE_BODY=$$(jq -r 'map(select(.title == "Alert monitor: noise log")) | .[0].body // ""' \
|
||||
@@ -238,7 +238,7 @@ steps:
|
||||
exit 0
|
||||
fi
|
||||
jq . /tmp/alert-verdicts.json > /dev/null # fail loudly on malformed output
|
||||
API="https://gitea.renner.uno/api/v1/repos/konairius/homelab"
|
||||
API="https://gitea.renner.uno/api/v1/repos/k.renner/homelab"
|
||||
STAMP=$$(date -u +%Y-%m-%d)
|
||||
|
||||
gitea_write() {
|
||||
|
||||
@@ -48,13 +48,13 @@
|
||||
# OWNER SETUP — the token + secret above are already provisioned. The one
|
||||
# remaining step, to be run AFTER this merges to master (a cron references a
|
||||
# pipeline that must exist on the target branch), is to register the schedule:
|
||||
# woodpecker repo cron add --repo konairius/homelab \
|
||||
# woodpecker repo cron add --repo k.renner/homelab \
|
||||
# --name ci-claude-refresh --branch master --schedule '@weekly'
|
||||
# Weekly is plenty; the pipelines tolerate being a few days behind a release.
|
||||
#
|
||||
# Triggering:
|
||||
# - cron : the cron NAMED exactly "ci-claude-refresh" (see above).
|
||||
# - deploy : woodpecker-cli pipeline deploy konairius/homelab <n> ci-claude-refresh
|
||||
# - deploy : woodpecker-cli pipeline deploy k.renner/homelab <n> ci-claude-refresh
|
||||
# where <n> is an existing pipeline to promote.
|
||||
# - push : a change to the Containerfile (or this file) ON master forces a
|
||||
# rebuild of the current latest version so Containerfile fixes ship
|
||||
@@ -174,9 +174,9 @@ steps:
|
||||
# Both were validated non-empty by the decide step; re-assert before a
|
||||
# sed that would corrupt every pin if either were blank.
|
||||
[ -n "$$NEW" ] && [ -n "$$CUR" ] || { echo "empty version/current sentinel — aborting"; exit 1; }
|
||||
API="https://gitea.renner.uno/api/v1/repos/konairius/homelab"
|
||||
API="https://gitea.renner.uno/api/v1/repos/k.renner/homelab"
|
||||
BRANCH="ci-image/claude-$$NEW"
|
||||
if git ls-remote --heads "https://gitea.renner.uno/konairius/homelab.git" "$$BRANCH" | grep -q .; then
|
||||
if git ls-remote --heads "https://gitea.renner.uno/k.renner/homelab.git" "$$BRANCH" | grep -q .; then
|
||||
echo "Branch $$BRANCH already exists — a PR for $$NEW is already open. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
@@ -197,7 +197,7 @@ steps:
|
||||
git checkout -b "$$BRANCH"
|
||||
git add $$CONSUMERS
|
||||
git commit -m "chore(ci): bump ci-claude image to $$NEW"
|
||||
git push "https://$$PUSH_TOKEN@gitea.renner.uno/konairius/homelab.git" HEAD:"$$BRANCH"
|
||||
git push "https://$$PUSH_TOKEN@gitea.renner.uno/k.renner/homelab.git" HEAD:"$$BRANCH"
|
||||
{
|
||||
printf 'Automated bump of the ci-claude agent image from `%s` to `%s`.\n\n' "$$CUR" "$$NEW"
|
||||
printf 'The refresh pipeline built and pushed `ci-claude:%s` (and `:latest`) from the\n' "$$NEW"
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
# was harmless at daily/weekly cadence — but alert-monitor.yml
|
||||
# fires every 15 minutes, so piggybacking must be opt-in now.
|
||||
# A new cron that SHOULD also run e2e gets its name added here.
|
||||
# - deploy : `woodpecker-cli pipeline deploy konairius/homelab <n> e2e`, where
|
||||
# - deploy : `woodpecker-cli pipeline deploy k.renner/homelab <n> e2e`, where
|
||||
# <n> is an existing pipeline to promote.
|
||||
|
||||
when:
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
#
|
||||
# Triggering:
|
||||
# - cron : a cron NAMED exactly "issue-fix":
|
||||
# woodpecker repo cron add --repo konairius/homelab \
|
||||
# woodpecker repo cron add --repo k.renner/homelab \
|
||||
# --name issue-fix --branch master --schedule '41 * * * *'
|
||||
# - deploy : woodpecker-cli pipeline deploy konairius/homelab <n> issue-fix
|
||||
# - deploy : woodpecker-cli pipeline deploy k.renner/homelab <n> issue-fix
|
||||
# where <n> is an existing pipeline to promote.
|
||||
when:
|
||||
- event: cron
|
||||
@@ -82,7 +82,7 @@ steps:
|
||||
set -eu
|
||||
TRUSTED_AUTHORS="k.renner" # space-separated; the trust anchor (see header)
|
||||
mkdir -p /tmp/alert-fix
|
||||
API="https://gitea.renner.uno/api/v1/repos/konairius/homelab"
|
||||
API="https://gitea.renner.uno/api/v1/repos/k.renner/homelab"
|
||||
curl -sfL "$$API/issues?state=open&type=issues&limit=100" \
|
||||
| jq '[.[] | select(.title | startswith("[alert] "))] | sort_by(.number)' \
|
||||
> /tmp/alert-fix/candidates.json
|
||||
@@ -163,7 +163,7 @@ steps:
|
||||
# run skips this issue, and stop cleanly.
|
||||
[ -f /tmp/alert-fix/issue.json ] || exit 0
|
||||
set -eu
|
||||
API="https://gitea.renner.uno/api/v1/repos/konairius/homelab"
|
||||
API="https://gitea.renner.uno/api/v1/repos/k.renner/homelab"
|
||||
NUM=$$(jq -r '.number' /tmp/alert-fix/issue.json)
|
||||
if git diff --quiet && git diff --cached --quiet \
|
||||
&& [ -z "$$(git ls-files --others --exclude-standard)" ]; then
|
||||
@@ -187,7 +187,7 @@ steps:
|
||||
[ -f /tmp/alert-fix/issue.json ] || exit 0
|
||||
if [ -f /tmp/alert-fix/no-edits ]; then exit 0; fi
|
||||
set -eu
|
||||
API="https://gitea.renner.uno/api/v1/repos/konairius/homelab"
|
||||
API="https://gitea.renner.uno/api/v1/repos/k.renner/homelab"
|
||||
NUM=$$(jq -r '.number' /tmp/alert-fix/issue.json)
|
||||
BRANCH=$$(cat /tmp/alert-fix/branch)
|
||||
TITLE="fix: $$(jq -r '.title' /tmp/alert-fix/issue.json) (#$$NUM)"
|
||||
@@ -196,7 +196,7 @@ steps:
|
||||
printf '\n\nCloses #%s\n' "$$NUM" >> /tmp/alert-fix/summary.md
|
||||
git add -A
|
||||
git commit -m "$$TITLE"
|
||||
git push "https://$$PUSH_TOKEN@gitea.renner.uno/konairius/homelab.git" HEAD:"$$BRANCH"
|
||||
git push "https://$$PUSH_TOKEN@gitea.renner.uno/k.renner/homelab.git" HEAD:"$$BRANCH"
|
||||
jq -n --arg title "$$TITLE" --rawfile body /tmp/alert-fix/summary.md \
|
||||
--arg head "$$BRANCH" \
|
||||
'{title: $$title, body: $$body, head: $$head, base: "master"}' > /tmp/gitea-pr.json
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
# pull requests. This pipeline only starts when triggered by hand and is kept for reference.
|
||||
#
|
||||
# PR review bot — per-repo STUB. All logic lives centrally in
|
||||
# konairius/homelab: ci/agents/pr-review/run.sh (plus prompt.md,
|
||||
# k.renner/homelab: ci/agents/pr-review/run.sh (plus prompt.md,
|
||||
# settings.json and scripts/ci/). This file is deployed byte-identical into every
|
||||
# reviewed repo (konairius/homelab, shelly-manager, authentik-samba-bridge,
|
||||
# reviewed repo (k.renner/homelab, shelly-manager, authentik-samba-bridge,
|
||||
# homeassistant) and holds NO logic, so the copies cannot drift apart: change the
|
||||
# review in homelab and every repo picks it up on its next PR event. Only edit this
|
||||
# stub if the trigger, the image or the secrets change — then change all copies in
|
||||
@@ -43,5 +43,5 @@ steps:
|
||||
from_secret: review_bot_token
|
||||
commands:
|
||||
- rm -rf /tmp/pr-review-central
|
||||
- git clone --quiet --depth 1 https://gitea.renner.uno/konairius/homelab /tmp/pr-review-central
|
||||
- git clone --quiet --depth 1 https://gitea.renner.uno/k.renner/homelab /tmp/pr-review-central
|
||||
- sh /tmp/pr-review-central/ci/agents/pr-review/run.sh
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# - cron : the named `daily-security-scan` cron, matched by NAME.
|
||||
# Crons cannot carry variables — `woodpecker-cli repo cron add`
|
||||
# has no --var — so a variable gate can never match a cron.
|
||||
# - deployment : woodpecker-cli pipeline deploy konairius/homelab <n> security-scan
|
||||
# - deployment : woodpecker-cli pipeline deploy k.renner/homelab <n> security-scan
|
||||
# The target rides in pipeline metadata as
|
||||
# CI_PIPELINE_DEPLOY_TARGET, which the workflow-root filter
|
||||
# does see, unlike manual-run variables on Woodpecker 3.18
|
||||
@@ -147,7 +147,7 @@ steps:
|
||||
TITLE="security: agent-proposed fixes ($$(date +%Y-%m-%d))"
|
||||
git add -A
|
||||
git commit -m "$$TITLE"
|
||||
git push "https://$$GITEA_TOKEN@gitea.renner.uno/konairius/homelab.git" HEAD:"$$BRANCH"
|
||||
git push "https://$$GITEA_TOKEN@gitea.renner.uno/k.renner/homelab.git" HEAD:"$$BRANCH"
|
||||
jq -n \
|
||||
--arg title "$$TITLE" \
|
||||
--arg body "$$BODY" \
|
||||
@@ -157,5 +157,5 @@ steps:
|
||||
-H "Authorization: token $$GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data @- \
|
||||
https://gitea.renner.uno/api/v1/repos/konairius/homelab/pulls \
|
||||
https://gitea.renner.uno/api/v1/repos/k.renner/homelab/pulls \
|
||||
| jq '{number, html_url, title}'
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# `repo cron add` takes name, branch, schedule and enabled — so the
|
||||
# name filter is the only thing that can select a workflow for a
|
||||
# scheduled run:
|
||||
# woodpecker repo cron add --repo konairius/homelab \
|
||||
# woodpecker repo cron add --repo k.renner/homelab \
|
||||
# --name update-audit --branch master --schedule '@weekly'
|
||||
# - deploy : woodpecker-cli pipeline deploy konairius/homelab <n> update-audit
|
||||
# - deploy : woodpecker-cli pipeline deploy k.renner/homelab <n> update-audit
|
||||
# where <n> is an existing pipeline to promote.
|
||||
#
|
||||
# NOTE: e2e.yml runs on an explicit list of cron names that includes this one,
|
||||
@@ -111,7 +111,7 @@ steps:
|
||||
set -eu
|
||||
TITLE="Infrastructure update audit"
|
||||
STAMP=$$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
API="https://gitea.renner.uno/api/v1/repos/konairius/homelab"
|
||||
API="https://gitea.renner.uno/api/v1/repos/k.renner/homelab"
|
||||
|
||||
# write <method> <url> <payload-file> -> prints HTTP code, fails non-2xx
|
||||
gitea_write() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
# PR review bot — the ONE implementation, run by every reviewed repo's
|
||||
# .woodpecker/pr-review.yml stub (konairius/homelab, shelly-manager,
|
||||
# .woodpecker/pr-review.yml stub (k.renner/homelab, shelly-manager,
|
||||
# authentik-samba-bridge, homeassistant). Edit it here only; the stubs hold no
|
||||
# logic. Design notes (event-driven trigger, dedup marker, tokens, model
|
||||
# selection, posture) are in the stub and in prompt.md.
|
||||
@@ -129,7 +129,7 @@ while IFS= read -r ITEM; do
|
||||
# # Cross-repo context for the review agent: shallow, UNAUTHENTICATED clones of
|
||||
# # the other fleet repos at ./.pr-review/context/<name>.
|
||||
# CTX="$D/context"; mkdir -p "$CTX"
|
||||
# for REPO in konairius/homelab konairius/homeassistant konairius/shelly-manager konairius/authentik-samba-bridge; do
|
||||
# for REPO in k.renner/homelab k.renner/homeassistant konairius/shelly-manager k.renner/authentik-samba-bridge; do
|
||||
# [ "$REPO" = "$SLUG" ] && continue
|
||||
# GIT_TERMINAL_PROMPT=0 git clone --quiet --depth 1 "https://gitea.renner.uno/$REPO" "$CTX/${REPO#*/}" 2>/dev/null \
|
||||
# || echo "No public context clone of $REPO (skipped)."
|
||||
|
||||
@@ -63,7 +63,7 @@ ARG E3ONCAN_REF=461dcbdf97e8d72d2edb9929c63b39382af6c3a1
|
||||
|
||||
LABEL org.opencontainers.image.title="heating-can" \
|
||||
org.opencontainers.image.description="open3e + E3onCAN, Viessmann E3 CAN bus readers" \
|
||||
org.opencontainers.image.source="https://gitea.renner.uno/konairius/homelab" \
|
||||
org.opencontainers.image.source="https://gitea.renner.uno/k.renner/homelab" \
|
||||
uno.renner.open3e.ref="${OPEN3E_REF}" \
|
||||
uno.renner.e3oncan.ref="${E3ONCAN_REF}"
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/app"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/kumaclient"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/app"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/kumaclient"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module gitea.renner.uno/konairius/homelab/kuma-sync
|
||||
module gitea.renner.uno/k.renner/homelab/kuma-sync
|
||||
|
||||
go 1.27
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
v1 "gitea.renner.uno/konairius/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/controller"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/kumaclient"
|
||||
v1 "gitea.renner.uno/k.renner/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/controller"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/kumaclient"
|
||||
)
|
||||
|
||||
// Options configures NewManager.
|
||||
|
||||
@@ -25,9 +25,9 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
v1 "gitea.renner.uno/konairius/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/app"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/kumaclient"
|
||||
v1 "gitea.renner.uno/k.renner/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/app"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/kumaclient"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
|
||||
v1 "gitea.renner.uno/konairius/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/kumaclient"
|
||||
v1 "gitea.renner.uno/k.renner/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/kumaclient"
|
||||
)
|
||||
|
||||
// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
|
||||
|
||||
v1 "gitea.renner.uno/konairius/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/kumaclient"
|
||||
v1 "gitea.renner.uno/k.renner/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/kumaclient"
|
||||
)
|
||||
|
||||
type gcEnv struct {
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
|
||||
v1 "gitea.renner.uno/konairius/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/kumaclient"
|
||||
v1 "gitea.renner.uno/k.renner/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/kumaclient"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
v1 "gitea.renner.uno/konairius/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/kumaclient"
|
||||
v1 "gitea.renner.uno/k.renner/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/kumaclient"
|
||||
)
|
||||
|
||||
// counting records calls on top of the Kuma fake.
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
v1 "gitea.renner.uno/konairius/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/kumaclient"
|
||||
v1 "gitea.renner.uno/k.renner/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/kumaclient"
|
||||
)
|
||||
|
||||
// defaultMaxRedirects is Kuma's default for a monitor with no explicit value.
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
v1 "gitea.renner.uno/konairius/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/konairius/homelab/kuma-sync/internal/kumaclient"
|
||||
v1 "gitea.renner.uno/k.renner/homelab/kuma-sync/api/v1alpha1"
|
||||
"gitea.renner.uno/k.renner/homelab/kuma-sync/internal/kumaclient"
|
||||
)
|
||||
|
||||
func TestMappingAllTypes(t *testing.T) {
|
||||
|
||||
@@ -35,7 +35,7 @@ spec:
|
||||
image: gitea.renner.uno/gitea_admin/review-bot:2026-10-19-0540 # {"$imagepolicy": "flux-system:review-bot"}
|
||||
env:
|
||||
- name: REPOS
|
||||
value: konairius/homelab,konairius/homeassistant,konairius/authentik-samba-bridge,konairius/shelly-manager
|
||||
value: k.renner/homelab,konairius/homeassistant,konairius/authentik-samba-bridge,konairius/shelly-manager
|
||||
- name: GITEA_URL
|
||||
value: https://gitea.renner.uno
|
||||
- name: AGENT_NAME
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
fsGroup: 33
|
||||
containers:
|
||||
- name: wordpress
|
||||
image: wordpress:7.1.3-php8.5-apache@sha256:ce31904fa1e51b73543fa2e35cc8f1747589b2abf48dc747b422749b88b77e9e
|
||||
image: wordpress:7.1.3-php8.3-apache@sha256:8ecd8d8e374167ea22235acf30f80d1165b82f07ffa5bcef90c1cad8f7be251f
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
|
||||
@@ -11,7 +11,7 @@ spec:
|
||||
branch: master
|
||||
secretRef:
|
||||
name: flux-system
|
||||
url: https://gitea.renner.uno/konairius/homelab.git
|
||||
url: https://gitea.renner.uno/k.renner/homelab.git
|
||||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
|
||||
@@ -4,7 +4,7 @@ metadata:
|
||||
name: gitea-mcp
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: docker.gitea.com/gitea-mcp-server@sha256:20701c3dcf3b71df384417d04f9f6382205b7f362380b258fb8e4dcf9599091f
|
||||
image: docker.gitea.com/gitea-mcp-server
|
||||
interval: 1h
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
|
||||
@@ -112,36 +112,3 @@ spec:
|
||||
memory: 16Mi
|
||||
limits:
|
||||
memory: 48Mi
|
||||
- name: sync-woodpecker-konairius
|
||||
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-07-30
|
||||
args: ["woodpecker-secret"]
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
env:
|
||||
- name: BAO_ADDR
|
||||
value: http://openbao.openbao.svc.cluster.local:8200
|
||||
- name: BAO_ROLE
|
||||
value: rotator-claude-oauth
|
||||
- name: BOOTSTRAP_PATH
|
||||
value: homelab/infrastructure/secret-rotators-bootstrap/woodpecker-bot
|
||||
- name: BAO_PATH
|
||||
value: homelab/infrastructure/claude
|
||||
- name: BAO_FIELD
|
||||
value: setup-token
|
||||
- name: WOODPECKER_URL
|
||||
value: http://woodpecker.woodpecker.svc.cluster.local:8000
|
||||
- name: WOODPECKER_ORG
|
||||
value: konairius
|
||||
- name: WOODPECKER_SECRET_NAME
|
||||
value: claude_code_oauth_token
|
||||
- name: WOODPECKER_EVENTS
|
||||
value: cron,deployment,manual,push,release,tag,pull_request
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
memory: 48Mi
|
||||
|
||||
@@ -78,10 +78,10 @@ spec:
|
||||
- name: GITEA_URL
|
||||
value: http://gitea.gitea.svc.cluster.local:3000
|
||||
- name: GITEA_VERIFY_REPO
|
||||
value: konairius/homelab
|
||||
value: k.renner/homelab
|
||||
- name: GITEA_SCOPES
|
||||
value: write:repository
|
||||
# Load-bearing: konairius/homelab is public, so the pull check
|
||||
# Load-bearing: k.renner/homelab is public, so the pull check
|
||||
# passes for ANY valid token — push (git-receive-pack, needs
|
||||
# write) is the only real verification. Pinned explicitly so the
|
||||
# safety property is visible here, not just a binary default.
|
||||
|
||||
@@ -197,36 +197,3 @@ spec:
|
||||
memory: 16Mi
|
||||
limits:
|
||||
memory: 48Mi
|
||||
- name: sync-woodpecker-konairius
|
||||
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-07-30
|
||||
args: ["woodpecker-secret"]
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
env:
|
||||
- name: BAO_ADDR
|
||||
value: http://openbao.openbao.svc.cluster.local:8200
|
||||
- name: BAO_ROLE
|
||||
value: rotator-review-bot
|
||||
- name: BOOTSTRAP_PATH
|
||||
value: homelab/infrastructure/secret-rotators-bootstrap/woodpecker-bot
|
||||
- name: BAO_PATH
|
||||
value: homelab/infrastructure/review-bot
|
||||
- name: BAO_FIELD
|
||||
value: token
|
||||
- name: WOODPECKER_URL
|
||||
value: http://woodpecker.woodpecker.svc.cluster.local:8000
|
||||
- name: WOODPECKER_ORG
|
||||
value: konairius
|
||||
- name: WOODPECKER_SECRET_NAME
|
||||
value: review_bot_token
|
||||
- name: WOODPECKER_EVENTS
|
||||
value: cron,manual,pull_request
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
memory: 48Mi
|
||||
|
||||
@@ -107,7 +107,7 @@ func TestAnthropicResumesAPausedTurnAndTakesTheFinalText(t *testing.T) {
|
||||
messageStream("claude-opus-5-5", "end_turn", block(0, toolUse), block(1, toolResult),
|
||||
textBlock(2, "## Summary\n\nok\n\nVerdict: comment")),
|
||||
}
|
||||
a, err := f.backend().Ask(context.Background(), "claude-opus-5-5", "Review pull request #7 of konairius/homelab.")
|
||||
a, err := f.backend().Ask(context.Background(), "claude-opus-5-5", "Review pull request #7 of k.renner/homelab.")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -204,14 +204,14 @@ func TestSelectorPicksTheBackend(t *testing.T) {
|
||||
f.rev.setReady("anthropic", true)
|
||||
f.rev.selector = &TypeSafe{url: ts.URL, key: "ts-key", model: "jev-latest", http: &http.Client{Timeout: 5 * time.Second}}
|
||||
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
posted := f.postedReviews()
|
||||
if len(posted) != 1 || !strings.Contains(posted[0]["body"], "model `"+tc.wantModel+"`") {
|
||||
t.Fatalf("want one review by %s, got %v", tc.wantModel, posted)
|
||||
}
|
||||
if pr := state["pull_request"].(map[string]any); pr["title"] != "feat: x" || pr["repository"] != "konairius/homelab" {
|
||||
if pr := state["pull_request"].(map[string]any); pr["title"] != "feat: x" || pr["repository"] != "k.renner/homelab" {
|
||||
t.Errorf("selector state: %v", state)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ func noFiles(string) ([]byte, error) { return nil, errors.New("no such file") }
|
||||
func baseEnv() map[string]string {
|
||||
return map[string]string{
|
||||
"REVIEW_TOKEN": "t", "WEBHOOK_SECRET": "s", "MISTRAL_API_KEY": "k",
|
||||
"CONNECTOR_ID": "c1", "REPOS": "konairius/homelab, konairius/shelly-manager",
|
||||
"CONNECTOR_ID": "c1", "REPOS": "k.renner/homelab, konairius/shelly-manager",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
module gitea.renner.uno/konairius/homelab/review-bot
|
||||
module gitea.renner.uno/k.renner/homelab/review-bot
|
||||
|
||||
go 1.27
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestBrokenStreamIsAnsweredFromTheHistory(t *testing.T) {
|
||||
{"type":"message.input","content":"Review pull request #7"},
|
||||
{"type":"tool.execution","name":"conn_get_file_contents"},
|
||||
{"type":"message.output","content":"## Summary\n\nfine\n\n## Recommendation\n\nLooks reasonable.\n\nVerdict: comment"}]}`
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.convCalls != 1 || f.historyCalls != 1 || len(f.postedReviews()) != 1 {
|
||||
@@ -26,7 +26,7 @@ func TestBrokenStreamIsAnsweredFromTheHistory(t *testing.T) {
|
||||
func TestBrokenStreamWithoutHistoryIsAskedAgain(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.breakStreams = 1
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.convCalls != 2 || f.historyCalls != 2 || len(f.postedReviews()) != 1 {
|
||||
@@ -37,7 +37,7 @@ func TestBrokenStreamWithoutHistoryIsAskedAgain(t *testing.T) {
|
||||
func TestStreamErrorEventIsNotRetried(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.streamError = "Failed to generate response."
|
||||
err := f.rev.Review(context.Background(), "konairius/homelab", 7)
|
||||
err := f.rev.Review(context.Background(), "k.renner/homelab", 7)
|
||||
if err == nil || !strings.Contains(err.Error(), "Failed to generate response.") {
|
||||
t.Fatalf("want the stream error, got %v", err)
|
||||
}
|
||||
|
||||
+35
-35
@@ -109,22 +109,22 @@ func newFixture(t *testing.T) *fixture {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
switch {
|
||||
case r.Method == "GET" && r.URL.Path == "/api/v1/repos/konairius/homelab/pulls/7":
|
||||
case r.Method == "GET" && r.URL.Path == "/api/v1/repos/k.renner/homelab/pulls/7":
|
||||
_, _ = io.WriteString(w, prJSON)
|
||||
case r.Method == "GET" && r.URL.Path == "/api/v1/repos/konairius/homelab/pulls/7/reviews":
|
||||
case r.Method == "GET" && r.URL.Path == "/api/v1/repos/k.renner/homelab/pulls/7/reviews":
|
||||
_, _ = io.WriteString(w, f.reviews)
|
||||
case r.Method == "GET" && r.URL.Path == "/api/v1/repos/konairius/homelab/pulls":
|
||||
case r.Method == "GET" && r.URL.Path == "/api/v1/repos/k.renner/homelab/pulls":
|
||||
_, _ = io.WriteString(w, "["+prJSON+"]")
|
||||
case r.Method == "POST" && r.URL.Path == "/api/v1/repos/konairius/homelab/pulls/7/reviews":
|
||||
case r.Method == "POST" && r.URL.Path == "/api/v1/repos/k.renner/homelab/pulls/7/reviews":
|
||||
var in map[string]string
|
||||
_ = json.NewDecoder(r.Body).Decode(&in)
|
||||
f.posted = append(f.posted, in)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = io.WriteString(w, `{"html_url":"https://gitea/pr/7#review"}`)
|
||||
case r.Method == "POST" && strings.HasPrefix(r.URL.Path, "/api/v1/repos/konairius/homelab/statuses/"):
|
||||
case r.Method == "POST" && strings.HasPrefix(r.URL.Path, "/api/v1/repos/k.renner/homelab/statuses/"):
|
||||
var in map[string]string
|
||||
_ = json.NewDecoder(r.Body).Decode(&in)
|
||||
in["sha"] = strings.TrimPrefix(r.URL.Path, "/api/v1/repos/konairius/homelab/statuses/")
|
||||
in["sha"] = strings.TrimPrefix(r.URL.Path, "/api/v1/repos/k.renner/homelab/statuses/")
|
||||
f.statuses = append(f.statuses, in)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = io.WriteString(w, "{}")
|
||||
@@ -152,7 +152,7 @@ func newFixture(t *testing.T) *fixture {
|
||||
var in map[string]any
|
||||
_ = json.NewDecoder(r.Body).Decode(&in)
|
||||
if in["agent_id"] != "ag_1" || in["stream"] != true || in["store"] != true ||
|
||||
!strings.Contains(in["inputs"].(string), "pull request #7 of konairius/homelab") {
|
||||
!strings.Contains(in["inputs"].(string), "pull request #7 of k.renner/homelab") {
|
||||
http.Error(w, "unexpected conversation request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -197,7 +197,7 @@ func newFixture(t *testing.T) *fixture {
|
||||
GiteaURL: f.gitea.URL, BotLogin: "review-bot", ReviewToken: "review-token", WebhookSecret: "hook-secret",
|
||||
MistralURL: f.mistral.URL, MistralKey: "mistral-key", AgentName: "pr-review",
|
||||
Models: []Model{{Backend: "mistral", Name: "mistral-large-4-0"}},
|
||||
ConnectorID: "c1", Repos: []string{"konairius/homelab"}, Workers: 1, QueueSize: 4,
|
||||
ConnectorID: "c1", Repos: []string{"k.renner/homelab"}, Workers: 1, QueueSize: 4,
|
||||
ReviewTimeout: 5 * time.Second, RateLimitRetries: 2, RateLimitWait: time.Millisecond,
|
||||
}
|
||||
f.metrics = newMetrics()
|
||||
@@ -245,7 +245,7 @@ func (f *fixture) postedReviews() []map[string]string {
|
||||
|
||||
func TestReviewPostsTheCleanedReport(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := f.postedReviews()
|
||||
@@ -272,7 +272,7 @@ func TestReviewPostsTheCleanedReport(t *testing.T) {
|
||||
func TestReviewSkipsWhatIsAlreadyReviewed(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.reviews = `[{"body":"<!-- pr-review:sha=abc123 -->","user":{"login":"review-bot"}}]`
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n := len(f.postedReviews()); n != 0 {
|
||||
@@ -286,7 +286,7 @@ func TestPendingReviewIsReviewedAgain(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.reviews = `[{"body":"<!-- pr-review:sha=abc123 -->","state":"PENDING","user":{"login":"review-bot"}}]`
|
||||
f.answer = `{"outputs":[{"type":"message.output","content":"## Summary\n\nfine\n\n## Recommendation\n\nLooks reasonable.\n\nVerdict: approve"}]}`
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
posted := f.postedReviews()
|
||||
@@ -299,7 +299,7 @@ func TestReviewRefusesWriteToolsAndPostsNothing(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.answer = `{"outputs":[{"type":"tool.execution","name":"conn_create_or_update_file"},
|
||||
{"type":"message.output","content":"## Summary\n\nx"}]}`
|
||||
err := f.rev.Review(context.Background(), "konairius/homelab", 7)
|
||||
err := f.rev.Review(context.Background(), "k.renner/homelab", 7)
|
||||
if err == nil || !strings.Contains(err.Error(), "conn_create_or_update_file") {
|
||||
t.Fatalf("want an error naming the tool, got %v", err)
|
||||
}
|
||||
@@ -311,7 +311,7 @@ func TestReviewRefusesWriteToolsAndPostsNothing(t *testing.T) {
|
||||
func TestReviewWithoutAReportPostsNothing(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.answer = `{"outputs":[{"type":"message.output","content":"I could not do it."}]}`
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err == nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err == nil {
|
||||
t.Fatal("want an error")
|
||||
}
|
||||
if n := len(f.postedReviews()); n != 0 {
|
||||
@@ -322,7 +322,7 @@ func TestReviewWithoutAReportPostsNothing(t *testing.T) {
|
||||
func TestReviewNeedsTheAgent(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.rev.setReady("mistral", false)
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err == nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err == nil {
|
||||
t.Fatal("want an error while the agent is not ready")
|
||||
}
|
||||
if f.rev.Ready() {
|
||||
@@ -333,7 +333,7 @@ func TestReviewNeedsTheAgent(t *testing.T) {
|
||||
func TestReviewAsksAgainAfterTheRateLimit(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.convStatus = []int{429, 429}
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.convCalls != 3 || len(f.postedReviews()) != 1 {
|
||||
@@ -348,7 +348,7 @@ func TestReviewHonoursRetryAfter(t *testing.T) {
|
||||
f.rev.cfg.RateLimitWait = 10 * time.Minute
|
||||
f.retryAfter = "0"
|
||||
f.convStatus = []int{429}
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.convCalls != 2 || len(f.postedReviews()) != 1 {
|
||||
@@ -371,7 +371,7 @@ func TestLimitHeaders(t *testing.T) {
|
||||
func TestReviewGivesUpWhenTheRateLimitStays(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.convStatus = []int{429, 429, 429, 429}
|
||||
err := f.rev.Review(context.Background(), "konairius/homelab", 7)
|
||||
err := f.rev.Review(context.Background(), "k.renner/homelab", 7)
|
||||
if err == nil || !strings.Contains(err.Error(), "still failing") {
|
||||
t.Fatalf("want a rate limit error, got %v", err)
|
||||
}
|
||||
@@ -383,7 +383,7 @@ func TestReviewGivesUpWhenTheRateLimitStays(t *testing.T) {
|
||||
func TestOtherErrorsAreNotRetried(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.convStatus = []int{500}
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err == nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err == nil {
|
||||
t.Fatal("want an error")
|
||||
}
|
||||
if f.convCalls != 1 {
|
||||
@@ -440,7 +440,7 @@ func (f *fixture) hook(event, signature string, body string) *httptest.ResponseR
|
||||
|
||||
func TestHook(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
body := `{"action":"opened","number":7,"repository":{"full_name":"konairius/homelab"}}`
|
||||
body := `{"action":"opened","number":7,"repository":{"full_name":"k.renner/homelab"}}`
|
||||
good := sign("hook-secret", []byte(body))
|
||||
cases := []struct {
|
||||
name, event, sig, body string
|
||||
@@ -455,10 +455,10 @@ func TestHook(t *testing.T) {
|
||||
strings.Replace(body, "opened", "closed", 1), 200},
|
||||
{"synchronized", "pull_request", sign("hook-secret", []byte(strings.Replace(body, "opened", "synchronized", 1))),
|
||||
strings.Replace(body, "opened", "synchronized", 1), 202},
|
||||
{"repository that is not configured", "pull_request", sign("hook-secret", []byte(strings.Replace(body, "konairius/homelab", "x/y", 1))),
|
||||
strings.Replace(body, "konairius/homelab", "x/y", 1), 200},
|
||||
{"payload without a number", "pull_request", sign("hook-secret", []byte(`{"action":"opened","repository":{"full_name":"konairius/homelab"}}`)),
|
||||
`{"action":"opened","repository":{"full_name":"konairius/homelab"}}`, 400},
|
||||
{"repository that is not configured", "pull_request", sign("hook-secret", []byte(strings.Replace(body, "k.renner/homelab", "x/y", 1))),
|
||||
strings.Replace(body, "k.renner/homelab", "x/y", 1), 200},
|
||||
{"payload without a number", "pull_request", sign("hook-secret", []byte(`{"action":"opened","repository":{"full_name":"k.renner/homelab"}}`)),
|
||||
`{"action":"opened","repository":{"full_name":"k.renner/homelab"}}`, 400},
|
||||
{"payload that is not json", "pull_request", sign("hook-secret", []byte("nope")), "nope", 400},
|
||||
}
|
||||
for _, c := range cases {
|
||||
@@ -473,10 +473,10 @@ func TestHook(t *testing.T) {
|
||||
func TestQueueRunsAReviewAndDeduplicates(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
q := newQueue(f.rev, 4, f.rev.log, f.metrics)
|
||||
if r := q.Enqueue("konairius/homelab", 7, "abc123"); r != "queued" {
|
||||
if r := q.Enqueue("k.renner/homelab", 7, "abc123"); r != "queued" {
|
||||
t.Fatalf("first enqueue: %s", r)
|
||||
}
|
||||
if r := q.Enqueue("konairius/homelab", 7, "abc123"); r != "duplicate" {
|
||||
if r := q.Enqueue("k.renner/homelab", 7, "abc123"); r != "duplicate" {
|
||||
t.Fatalf("second enqueue of the same PR: %s", r)
|
||||
}
|
||||
q.Start(context.Background(), 1)
|
||||
@@ -484,7 +484,7 @@ func TestQueueRunsAReviewAndDeduplicates(t *testing.T) {
|
||||
if n := len(f.postedReviews()); n != 1 {
|
||||
t.Errorf("posted %d reviews, want 1", n)
|
||||
}
|
||||
if r := q.Enqueue("konairius/homelab", 8, ""); r != "full" {
|
||||
if r := q.Enqueue("k.renner/homelab", 8, ""); r != "full" {
|
||||
t.Errorf("a stopped queue takes nothing, got %s", r)
|
||||
}
|
||||
}
|
||||
@@ -529,7 +529,7 @@ func TestFirstRetryWaitsTheConfiguredTime(t *testing.T) {
|
||||
f.rev.cfg.RateLimitWait = 300 * time.Millisecond
|
||||
f.convStatus = []int{429}
|
||||
start := time.Now()
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := time.Since(start); got < 250*time.Millisecond {
|
||||
@@ -566,7 +566,7 @@ func TestReviewOfAFileAboutRateLimitsIsNotDropped(t *testing.T) {
|
||||
f.answer = `{"outputs":[
|
||||
{"type":"tool.execution","name":"conn_get_file_contents","info":{"result":"var rateLimitText = rate limit handling"}},
|
||||
{"type":"message.output","content":"## Summary\n\nreviewed\n\n## Recommendation\n\nApprove."}]}`
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.convCalls != 1 || len(f.postedReviews()) != 1 {
|
||||
@@ -579,7 +579,7 @@ func TestReviewOfAFileAboutRateLimitsIsNotDropped(t *testing.T) {
|
||||
func TestReviewDoesNotPostForAStaleHead(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.pushAfter = "def456"
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
posted := f.postedReviews()
|
||||
@@ -595,18 +595,18 @@ func TestReviewDoesNotPostForAStaleHead(t *testing.T) {
|
||||
func TestQueueReviewsAgainWhenAskedForDuringTheReview(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
q := newQueue(f.rev, 4, f.rev.log, f.metrics)
|
||||
if r := q.Enqueue("konairius/homelab", 7, "abc123"); r != "queued" {
|
||||
if r := q.Enqueue("k.renner/homelab", 7, "abc123"); r != "queued" {
|
||||
t.Fatalf("enqueue: %s", r)
|
||||
}
|
||||
q.mu.Lock()
|
||||
q.running["konairius/homelab#7"] = true // as if a worker had picked it up
|
||||
q.running["k.renner/homelab#7"] = true // as if a worker had picked it up
|
||||
q.mu.Unlock()
|
||||
if r := q.Enqueue("konairius/homelab", 7, "abc123"); r != "duplicate" {
|
||||
if r := q.Enqueue("k.renner/homelab", 7, "abc123"); r != "duplicate" {
|
||||
t.Fatalf("enqueue while running: %s", r)
|
||||
}
|
||||
q.mu.Lock()
|
||||
again := q.again["konairius/homelab#7"]
|
||||
delete(q.running, "konairius/homelab#7")
|
||||
again := q.again["k.renner/homelab#7"]
|
||||
delete(q.running, "k.renner/homelab#7")
|
||||
q.mu.Unlock()
|
||||
if !again {
|
||||
t.Error("the request during the review was forgotten")
|
||||
@@ -617,7 +617,7 @@ func TestReviewAsksAgainAfterAGatewayFailure(t *testing.T) {
|
||||
for _, status := range []int{502, 503, 504} {
|
||||
f := newFixture(t)
|
||||
f.convStatus = []int{status}
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatalf("%d: %v", status, err)
|
||||
}
|
||||
if f.convCalls != 2 || len(f.postedReviews()) != 1 {
|
||||
|
||||
+17
-17
@@ -13,10 +13,10 @@ import (
|
||||
func TestQueueReportsQueuedRunningAndPosted(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
q := newQueue(f.rev, 4, f.rev.log, f.metrics)
|
||||
if r := q.Enqueue("konairius/homelab", 7, "abc123"); r != "queued" {
|
||||
if r := q.Enqueue("k.renner/homelab", 7, "abc123"); r != "queued" {
|
||||
t.Fatalf("enqueue: %s", r)
|
||||
}
|
||||
if st, _ := f.status.Get("konairius/homelab", 7); st.Phase != PhaseQueued || st.Position != 1 {
|
||||
if st, _ := f.status.Get("k.renner/homelab", 7); st.Phase != PhaseQueued || st.Position != 1 {
|
||||
t.Errorf("before the worker runs: %+v, want queued at position 1", st)
|
||||
}
|
||||
q.Start(context.Background(), 1)
|
||||
@@ -30,7 +30,7 @@ func TestQueueReportsQueuedRunningAndPosted(t *testing.T) {
|
||||
if got := f.commitStatuses(); !slices.Equal(got, want) {
|
||||
t.Errorf("commit statuses\n got %q\nwant %q", got, want)
|
||||
}
|
||||
st, _ := f.status.Get("konairius/homelab", 7)
|
||||
st, _ := f.status.Get("k.renner/homelab", 7)
|
||||
if st.Phase != PhasePosted || st.URL != "https://gitea/pr/7#review" || st.Head != "abc123" {
|
||||
t.Errorf("after the review: %+v", st)
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func TestAFailedReviewIsReportedAsAnError(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.answer = `{"outputs":[{"type":"message.output","content":"I could not do it."}]}`
|
||||
q := newQueue(f.rev, 4, f.rev.log, f.metrics)
|
||||
q.Enqueue("konairius/homelab", 7, "abc123")
|
||||
q.Enqueue("k.renner/homelab", 7, "abc123")
|
||||
q.Start(context.Background(), 1)
|
||||
q.Stop(5 * time.Second)
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestAFailedReviewIsReportedAsAnError(t *testing.T) {
|
||||
if last := got[len(got)-1]; last != "abc123 error: review failed: the agent's answer holds no report" {
|
||||
t.Errorf("last commit status %q", last)
|
||||
}
|
||||
if st, _ := f.status.Get("konairius/homelab", 7); st.Phase != PhaseFailed || st.Detail != "the agent's answer holds no report" {
|
||||
if st, _ := f.status.Get("k.renner/homelab", 7); st.Phase != PhaseFailed || st.Detail != "the agent's answer holds no report" {
|
||||
t.Errorf("after the failure: %+v", st)
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func TestAFailedReviewIsReportedAsAnError(t *testing.T) {
|
||||
func TestARetryIsReported(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.convStatus = []int{429}
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := f.commitStatuses()
|
||||
@@ -70,12 +70,12 @@ func TestARetryIsReported(t *testing.T) {
|
||||
func TestSkipsResolveTheQueuedStatus(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.title = "WIP: x"
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.title = "feat: x"
|
||||
f.reviews = `[{"body":"<!-- pr-review:sha=abc123 -->","state":"APPROVED","html_url":"https://gitea/r/1","user":{"login":"review-bot"}}]`
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []string{"abc123 success: skipped: wip", "abc123 success: reviewed: approve"}
|
||||
@@ -87,7 +87,7 @@ func TestSkipsResolveTheQueuedStatus(t *testing.T) {
|
||||
func TestAStaleHeadIsMarkedSuperseded(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.pushAfter = "def456"
|
||||
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
||||
if err := f.rev.Review(context.Background(), "k.renner/homelab", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := f.commitStatuses()
|
||||
@@ -101,7 +101,7 @@ func TestStatusEndpoints(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
q := newQueue(f.rev, 4, f.rev.log, f.metrics)
|
||||
s := &Server{cfg: f.cfg, queue: q, reviewer: f.rev, metrics: f.metrics, log: f.rev.log}
|
||||
q.Enqueue("konairius/homelab", 7, "abc123")
|
||||
q.Enqueue("k.renner/homelab", 7, "abc123")
|
||||
get := func(path string) *httptest.ResponseRecorder {
|
||||
rec := httptest.NewRecorder()
|
||||
s.routes().ServeHTTP(rec, httptest.NewRequest("GET", path, nil))
|
||||
@@ -114,14 +114,14 @@ func TestStatusEndpoints(t *testing.T) {
|
||||
t.Errorf("/status: %d %s", rec.Code, rec.Body)
|
||||
}
|
||||
var one PRStatus
|
||||
if rec := get("/status/konairius/homelab/7"); rec.Code != 200 || json.Unmarshal(rec.Body.Bytes(), &one) != nil ||
|
||||
one.Repo != "konairius/homelab" || one.Number != 7 || one.Head != "abc123" {
|
||||
t.Errorf("/status/konairius/homelab/7: %d %s", rec.Code, rec.Body)
|
||||
if rec := get("/status/k.renner/homelab/7"); rec.Code != 200 || json.Unmarshal(rec.Body.Bytes(), &one) != nil ||
|
||||
one.Repo != "k.renner/homelab" || one.Number != 7 || one.Head != "abc123" {
|
||||
t.Errorf("/status/k.renner/homelab/7: %d %s", rec.Code, rec.Body)
|
||||
}
|
||||
if rec := get("/status/konairius/homelab/8"); rec.Code != 404 {
|
||||
if rec := get("/status/k.renner/homelab/8"); rec.Code != 404 {
|
||||
t.Errorf("untracked PR: %d, want 404", rec.Code)
|
||||
}
|
||||
if rec := get("/status/konairius/homelab/x"); rec.Code != 400 {
|
||||
if rec := get("/status/k.renner/homelab/x"); rec.Code != 400 {
|
||||
t.Errorf("bad number: %d, want 400", rec.Code)
|
||||
}
|
||||
}
|
||||
@@ -130,8 +130,8 @@ func TestFinishedReviewsAreForgottenAfterADay(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
now := time.Now()
|
||||
f.status.now = func() time.Time { return now }
|
||||
f.status.Set("konairius/homelab", 7, "", PhasePosted, "comment", "")
|
||||
f.status.Set("konairius/homelab", 8, "", PhaseQueued, "", "")
|
||||
f.status.Set("k.renner/homelab", 7, "", PhasePosted, "comment", "")
|
||||
f.status.Set("k.renner/homelab", 8, "", PhaseQueued, "", "")
|
||||
now = now.Add(keepFinished + time.Minute)
|
||||
got := f.status.List()
|
||||
if len(got) != 1 || got[0].Number != 8 {
|
||||
|
||||
Reference in New Issue
Block a user