apiVersion: tekton.dev/v1 kind: Task metadata: name: maidn-preview-orphan-reconciler namespace: tekton-pipelines annotations: # The environment GitOps repo owns this ConfigMap; render it before enabling this Task. maidn.io/delivery-config: maidn-preview-delivery-config # The TaskRun service account supplies this annotated Git Secret through Tekton's initializer. maidn.io/git-credentials: forgejo-git-credentials spec: stepTemplate: env: - name: HOME value: /tekton/home securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 allowPrivilegeEscalation: false capabilities: drop: - ALL seccompProfile: type: RuntimeDefault params: - name: app-name type: string - name: app-repository type: string - name: pr-number type: string steps: - name: verify-closed-pr image: python:3.13-alpine3.21 env: - name: TRUSTED_FORGEJO_ORIGIN valueFrom: configMapKeyRef: name: maidn-preview-delivery-config key: forgejo-origin - name: TRUSTED_MANIFESTS_URL valueFrom: configMapKeyRef: name: maidn-preview-delivery-config key: manifests-url - name: TRUSTED_MANIFESTS_BRANCH valueFrom: configMapKeyRef: name: maidn-preview-delivery-config key: manifests-branch - name: APP_NAME value: $(params.app-name) - name: APP_REPOSITORY value: $(params.app-repository) - name: PR_NUMBER value: $(params.pr-number) script: | #!/bin/sh set -eu fail() { exit 1; } valid_forgejo_origin() { case "$1" in https://*) ;; *) fail ;; esac host=${1#https://} case "$host" in ''|.*|*..*|*.) fail ;; esac case "$host" in *[!A-Za-z0-9.-]*) fail ;; esac } valid_git_url() { case "$1" in https://*/*.git) ;; *) fail ;; esac repository=${1#https://} host=${repository%%/*} path=${repository#*/} case "$host" in ''|.*|*..*|*.) fail ;; esac case "$host" in *[!A-Za-z0-9.-]*) fail ;; esac case "$path" in ''|*[!A-Za-z0-9._/-]*|/*|*//*|*..*) fail ;; esac [ "https://$host" = "$TRUSTED_FORGEJO_ORIGIN" ] || fail } valid_branch() { case "$1" in [A-Za-z0-9]*) ;; *) fail ;; esac case "$1" in *[!A-Za-z0-9._/-]*|*..*|*//*|*/) fail ;; esac } valid_name() { case "$1" in ''|*[!a-z0-9-]*|-*|*-) fail ;; esac [ "${#1}" -le 63 ] || fail } valid_repository() { case "$1" in */*) ;; *) fail ;; esac owner=${1%%/*} repository=${1#*/} [ "$owner/$repository" = "$1" ] || fail case "$owner" in ''|.*|*.|*[!A-Za-z0-9._-]*|*..*) fail ;; esac case "$repository" in ''|.*|*.|*[!A-Za-z0-9._-]*|*..*) fail ;; esac } valid_pr_number() { case "$1" in [1-9]*) ;; *) fail ;; esac case "$1" in *[!0-9]*) fail ;; esac [ $((${#APP_NAME} + ${#1} + 4)) -le 63 ] || fail } valid_forgejo_origin "$TRUSTED_FORGEJO_ORIGIN" valid_git_url "$TRUSTED_MANIFESTS_URL" valid_branch "$TRUSTED_MANIFESTS_BRANCH" valid_name "$APP_NAME" valid_repository "$APP_REPOSITORY" valid_pr_number "$PR_NUMBER" python3 - <<'PY' import base64 import json import os import sys from pathlib import Path from urllib.error import URLError from urllib.parse import urlsplit from urllib.request import HTTPRedirectHandler, Request, build_opener origin = urlsplit(os.environ["TRUSTED_FORGEJO_ORIGIN"]) try: lines = Path(os.environ["HOME"], ".git-credentials").read_text().splitlines() matches = [] for line in lines: credential = urlsplit(line) if ( credential.scheme == "https" and credential.hostname == origin.hostname and credential.path in ("", "/") and not credential.query and not credential.fragment and credential.username and credential.password ): matches.append((credential.username, credential.password)) except (OSError, ValueError): sys.exit(1) if len(matches) != 1: sys.exit(1) repository = os.environ["APP_REPOSITORY"] number = int(os.environ["PR_NUMBER"]) credentials = "%s:%s" % matches[0] request = Request("%s/api/v1/repos/%s/pulls/%s" % (os.environ["TRUSTED_FORGEJO_ORIGIN"], repository, number)) request.add_header("Authorization", "Basic " + base64.b64encode(credentials.encode()).decode()) request.add_header("Accept", "application/json") class NoRedirect(HTTPRedirectHandler): def redirect_request(self, request, fp, code, msg, headers, url): return None try: with build_opener(NoRedirect).open(request, timeout=20) as response: pull = json.load(response) except (URLError, ValueError, json.JSONDecodeError): sys.exit(1) if type(pull) is not dict or type(pull.get("number")) is not int or pull["number"] != number or pull.get("state") != "closed": sys.exit(1) PY - name: reconcile image: alpine/git:2.47.2 env: - name: TRUSTED_FORGEJO_ORIGIN valueFrom: configMapKeyRef: name: maidn-preview-delivery-config key: forgejo-origin - name: TRUSTED_MANIFESTS_URL valueFrom: configMapKeyRef: name: maidn-preview-delivery-config key: manifests-url - name: TRUSTED_MANIFESTS_BRANCH valueFrom: configMapKeyRef: name: maidn-preview-delivery-config key: manifests-branch - name: APP_NAME value: $(params.app-name) - name: APP_REPOSITORY value: $(params.app-repository) - name: PR_NUMBER value: $(params.pr-number) script: | #!/bin/sh set -eu fail() { exit 1; } valid_forgejo_origin() { case "$1" in https://*) ;; *) fail ;; esac host=${1#https://} case "$host" in ''|.*|*..*|*.) fail ;; esac case "$host" in *[!A-Za-z0-9.-]*) fail ;; esac } valid_git_url() { case "$1" in https://*/*.git) ;; *) fail ;; esac repository=${1#https://} host=${repository%%/*} path=${repository#*/} case "$host" in ''|.*|*..*|*.) fail ;; esac case "$host" in *[!A-Za-z0-9.-]*) fail ;; esac case "$path" in ''|*[!A-Za-z0-9._/-]*|/*|*//*|*..*) fail ;; esac [ "https://$host" = "$TRUSTED_FORGEJO_ORIGIN" ] || fail } valid_branch() { case "$1" in [A-Za-z0-9]*) ;; *) fail ;; esac case "$1" in *[!A-Za-z0-9._/-]*|*..*|*//*|*/) fail ;; esac } valid_name() { case "$1" in ''|*[!a-z0-9-]*|-*|*-) fail ;; esac [ "${#1}" -le 63 ] || fail } valid_repository() { case "$1" in */*) ;; *) fail ;; esac owner=${1%%/*} repository=${1#*/} [ "$owner/$repository" = "$1" ] || fail case "$owner" in ''|.*|*.|*[!A-Za-z0-9._-]*|*..*) fail ;; esac case "$repository" in ''|.*|*.|*[!A-Za-z0-9._-]*|*..*) fail ;; esac } valid_pr_number() { case "$1" in [1-9]*) ;; *) fail ;; esac case "$1" in *[!0-9]*) fail ;; esac [ $((${#APP_NAME} + ${#1} + 4)) -le 63 ] || fail } valid_forgejo_origin "$TRUSTED_FORGEJO_ORIGIN" valid_git_url "$TRUSTED_MANIFESTS_URL" valid_branch "$TRUSTED_MANIFESTS_BRANCH" valid_name "$APP_NAME" valid_repository "$APP_REPOSITORY" valid_pr_number "$PR_NUMBER" namespace="$APP_NAME-pr-$PR_NUMBER" preview_path="apps/previews/$namespace" umask 077 private_dir=$(mktemp -d) chmod 700 "$private_dir" trap 'rm -rf -- "$private_dir"' EXIT HUP INT TERM repository_path="$private_dir/manifests" git clone --branch "$TRUSTED_MANIFESTS_BRANCH" "$TRUSTED_MANIFESTS_URL" "$repository_path" [ "$(git -C "$repository_path" remote get-url origin)" = "$TRUSTED_MANIFESTS_URL" ] || fail git -C "$repository_path" rev-parse --verify "refs/heads/$TRUSTED_MANIFESTS_BRANCH" >/dev/null cd "$repository_path" [ -d apps ] && [ ! -L apps ] || fail [ -d apps/previews ] && [ ! -L apps/previews ] || fail [ -d "$preview_path" ] && [ ! -L "$preview_path" ] || fail marker="$preview_path/ownership.yaml" [ -f "$marker" ] && [ ! -L "$marker" ] || fail for existing in "$preview_path"/* "$preview_path"/.[!.]* "$preview_path"/..?*; do [ -e "$existing" ] || [ -L "$existing" ] || continue [ -f "$existing" ] && [ ! -L "$existing" ] || fail case "${existing##*/}" in namespace.yaml|ownership.yaml|values.yaml|kustomization.yaml|release.yaml) ;; *) fail ;; esac done expected_marker=$(mktemp "$private_dir/expected-marker.XXXXXX") cat > "$expected_marker" <