From da8ba776e8ee8695544389ee6d978903e110e83a Mon Sep 17 00:00:00 2001 From: Alex Lebens Date: Sat, 14 Mar 2026 23:07:57 -0500 Subject: [PATCH] feat: consolidate render workflows to 1 --- .../workflows/render-manifests-automerge.yaml | 15 +- .../workflows/render-manifests-dispatch.yaml | 7 +- .gitea/workflows/render-manifests-merge.yaml | 15 +- .gitea/workflows/render-manifests-push.yaml | 11 +- .gitea/workflows/render-manifests.yaml | 584 ++++++++++++++++++ 5 files changed, 610 insertions(+), 22 deletions(-) create mode 100644 .gitea/workflows/render-manifests.yaml diff --git a/.gitea/workflows/render-manifests-automerge.yaml b/.gitea/workflows/render-manifests-automerge.yaml index 9d9ac0fe6..d049080d9 100644 --- a/.gitea/workflows/render-manifests-automerge.yaml +++ b/.gitea/workflows/render-manifests-automerge.yaml @@ -1,13 +1,14 @@ name: render-manifests-automerge on: - pull_request: - branches: - - main - paths: - - 'clusters/cl01tl/helm/**' - types: - - closed + workflow_dispatch: + # pull_request: + # branches: + # - main + # paths: + # - 'clusters/cl01tl/helm/**' + # types: + # - closed env: CLUSTER: cl01tl diff --git a/.gitea/workflows/render-manifests-dispatch.yaml b/.gitea/workflows/render-manifests-dispatch.yaml index d26c74c30..6cfc8b517 100644 --- a/.gitea/workflows/render-manifests-dispatch.yaml +++ b/.gitea/workflows/render-manifests-dispatch.yaml @@ -1,10 +1,11 @@ name: render-manifests-dispatch on: - schedule: - - cron: '0 15 * * *' - workflow_dispatch: + # schedule: + # - cron: '0 15 * * *' + + # workflow_dispatch: env: CLUSTER: cl01tl diff --git a/.gitea/workflows/render-manifests-merge.yaml b/.gitea/workflows/render-manifests-merge.yaml index eb5e6fc2c..3a0468c36 100644 --- a/.gitea/workflows/render-manifests-merge.yaml +++ b/.gitea/workflows/render-manifests-merge.yaml @@ -1,13 +1,14 @@ name: render-manifests-merge on: - pull_request: - branches: - - main - paths: - - 'clusters/cl01tl/helm/**' - types: - - closed + workflow_dispatch: + # pull_request: + # branches: + # - main + # paths: + # - 'clusters/cl01tl/helm/**' + # types: + # - closed env: CLUSTER: cl01tl diff --git a/.gitea/workflows/render-manifests-push.yaml b/.gitea/workflows/render-manifests-push.yaml index d5ca0db4b..12c3c8ad4 100644 --- a/.gitea/workflows/render-manifests-push.yaml +++ b/.gitea/workflows/render-manifests-push.yaml @@ -1,11 +1,12 @@ name: render-manifests-push on: - push: - branches: - - main - paths: - - 'clusters/cl01tl/helm/**' + workflow_dispatch: + # push: + # branches: + # - main + # paths: + # - 'clusters/cl01tl/helm/**' env: CLUSTER: cl01tl diff --git a/.gitea/workflows/render-manifests.yaml b/.gitea/workflows/render-manifests.yaml new file mode 100644 index 000000000..db862782f --- /dev/null +++ b/.gitea/workflows/render-manifests.yaml @@ -0,0 +1,584 @@ +name: render-manifests + +on: + schedule: + - cron: '0 15 * * *' + + workflow_dispatch: + + push: + branches: + - main + paths: + - 'clusters/cl01tl/helm/**' + + pull_request: + branches: + - main + paths: + - 'clusters/cl01tl/helm/**' + types: + - closed + +env: + CLUSTER: cl01tl + BASE_BRANCH: manifests + BRANCH_NAME_BASE: auto/update-manifests + ASSIGNEE: alexlebens + MAIN_DIR: /workspace/alexlebens/infrastructure/infrastructure + MANIFEST_DIR: /workspace/alexlebens/infrastructure/infrastructure-manifests + +jobs: + render-manifests: + runs-on: ubuntu-js + if: >- + github.event_name == 'schedule' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && github.actor != 'renovate-bot') || + (github.event_name == 'pull_request' && github.event.pull_request.merged == true) + steps: + - name: Checkout Main + uses: actions/checkout@v6 + with: + path: infrastructure + fetch-depth: 0 + + - name: Checkout Manifests + uses: actions/checkout@v6 + with: + ref: manifests + path: infrastructure-manifests + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + token: ${{ secrets.GITEA_TOKEN }} + version: v3.17.2 # Pending https://github.com/helm/helm/pull/30743 + cache: true + + - name: Configure Kubeconfig + uses: azure/k8s-set-context@v4 + with: + method: kubeconfig + kubeconfig: ${{ secrets.KUBECONFIG }} + + - name: Cache Helm Dependencies + uses: actions/cache@v5 + with: + path: | + ~/.cache/helm + ~/.config/helm + key: helm-cache-${{ runner.os }}-${{ hashFiles('infrastructure/clusters/cl01tl/helm/**/Chart.yaml', 'infrastructure/clusters/cl01tl/helm/**/Chart.lock') }} + restore-keys: | + helm-cache-${{ runner.os }}- + + - name: Determine Workflow Mode + id: mode + run: | + IS_AUTOMERGE="false" + RENDER_ALL="false" + DIFF_TARGET="" + + if [[ "${{ github.event_name }}" == "schedule" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "" + echo ">> Mode: Dispatch/Schedule (Render All)" + RENDER_ALL="true" + + elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + if [[ "${{ contains(github.event.pull_request.labels.*.name, 'automerge') }}" == "true" ]]; then + echo "" + echo ">> Mode: PR Merged (Automerge)" + IS_AUTOMERGE="true" + + else + echo "" + echo ">> Mode: PR Merged (Standard)" + + fi + + DIFF_TARGET="HEAD^..HEAD" + + elif [[ "${{ github.event_name }}" == "push" ]]; then + echo "" + echo ">> Mode: Push (Standard)" + DIFF_TARGET="${{ github.event.before }}..HEAD" + + fi + + echo "----" + + echo "is_automerge=${IS_AUTOMERGE}" >> "$GITHUB_OUTPUT" + echo "render_all=${RENDER_ALL}" >> "$GITHUB_OUTPUT" + echo "diff_target=${DIFF_TARGET}" >> "$GITHUB_OUTPUT" + + - name: Prepare Manifest Branch + id: prepare-manifest-branch + env: + IS_AUTOMERGE: ${{ steps.mode.outputs.is_automerge }} + run: | + cd "${MANIFEST_DIR}" + + echo "" + echo ">> Configure git to use gitea-bot as user ..." + git config user.name "gitea-bot" + git config user.email "gitea-bot@alexlebens.net" + + if [[ "$IS_AUTOMERGE" == "true" ]]; then + echo "" + echo ">> Creating branch ${BRANCH_NAME} ..." + BRANCH_NAME="${BRANCH_NAME_BASE}-automerge-$(date +%Y%m%d%H%M%S)" + git checkout -b "$BRANCH_NAME" + + else + echo "" + echo ">> Checking if PR branch exists ..." + BRANCH_NAME="${BRANCH_NAME_BASE}" + + if git ls-remote --exit-code --heads origin "${BRANCH_NAME}" > /dev/null 2>&1; then + echo "" + echo ">> Branch '${BRANCH_NAME}' exists, pulling changes ..." + git fetch origin "${BRANCH_NAME}" + git checkout "${BRANCH_NAME}" + git pull --rebase + + else + echo "" + echo ">> Branch '${BRANCH_NAME}' does not exist, creating ..." + git checkout -b "${BRANCH_NAME}" + + fi + fi + + echo "----" + + echo "BRANCH_NAME=${BRANCH_NAME}" >> "$GITHUB_OUTPUT" + + - name: Check which Directories have Changes + id: check-dir-changes + env: + RENDER_ALL: ${{ steps.mode.outputs.render_all }} + DIFF_TARGET: ${{ steps.mode.outputs.diff_target }} + run: | + cd "${MAIN_DIR}" + + if [[ "$RENDER_ALL" == "true" ]]; then + echo "" + echo ">> Triggered on dispatch, will check all paths ..." + RENDER_DIR=$(find "clusters/${CLUSTER}/helm" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort -u) + + else + echo "" + echo ">> Checking for changes from ${DIFF_TARGET} ..." + RENDER_DIR=$(git diff --name-only "${DIFF_TARGET}" | grep -E "^clusters/${CLUSTER}/helm/" | awk -F '/' '{print $4}' | sort -u || true) + + fi + + if [ -n "${RENDER_DIR}" ]; then + echo "" + echo ">> Directories to Render:" + echo "${RENDER_DIR}" + + echo "----" + + echo "changes-detected=true" >> "$GITHUB_OUTPUT" + echo "render-dir-csv=$(echo "${RENDER_DIR}" | paste -sd ',' -)" >> "$GITHUB_OUTPUT" + echo "render-dir<> "$GITHUB_OUTPUT" + echo "${RENDER_DIR}" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + else + echo "" + echo ">> No chart changes detected" + + echo "----" + + echo "changes-detected=false" >> "$GITHUB_OUTPUT" + + fi + + - name: Add Repositories + if: steps.check-dir-changes.outputs.changes-detected == 'true' + env: + RENDER_DIR: ${{ steps.check-dir-changes.outputs.render-dir }} + run: | + cd "${MAIN_DIR}" + + echo "" + echo ">> Adding repositories for chart dependencies ..." + + for DIR in ${RENDER_DIR}; do + helm dependency list --max-col-width 120 "${MAIN_DIR}/clusters/${CLUSTER}/helm/${DIR}" 2> /dev/null \ + | tail -n +2 \ + | awk 'NF > 0 { print $1, $3 }' \ + | while read -r REPO_NAME REPO_URL; do + if [[ "${REPO_URL}" == oci://* ]]; then + echo ">> Ignoring OCI repo: ${REPO_URL}" + + elif [[ -n "${REPO_NAME}" && -n "${REPO_URL}" ]]; then + helm repo add "${REPO_NAME}" "${REPO_URL}" + + fi + + done || true + done + + if helm repo list > /dev/null 2>&1; then + echo "" + echo ">> Update repository cache ..." + helm repo update + + fi + + echo "----" + + - name: Remove Changed Manifest Files + if: steps.check-dir-changes.outputs.changes-detected == 'true' + env: + RENDER_DIR: ${{ steps.check-dir-changes.outputs.render-dir }} + run: | + cd "${MANIFEST_DIR}" + + echo "" + echo ">> Remove manifest files and rebuild from source ..." + + for DIR in ${RENDER_DIR}; do + CHART_PATH="${MANIFEST_DIR}/clusters/${CLUSTER}/manifests/${DIR}" + + echo "" + echo "${CHART_PATH}" + rm -rf "${CHART_PATH}"/* + + done + + echo "----" + + - name: Render Helm Manifests + id: render-manifests + if: steps.check-dir-changes.outputs.changes-detected == 'true' + env: + RENDER_DIR: ${{ steps.check-dir-changes.outputs.render-dir }} + run: | + cd "${MAIN_DIR}" + + echo "" + echo ">> Rendering Manifests ..." + + render_chart() { + local DIR="$1" + local CHART_PATH="${MAIN_DIR}/clusters/${CLUSTER}/helm/${DIR}" + local CHART_NAME=$(basename "${CHART_PATH}") + + echo "" + echo ">> Rendering chart: ${CHART_NAME}" + + if [ -f "${CHART_PATH}/Chart.yaml" ]; then + local OUTPUT_FOLDER="${MANIFEST_DIR}/clusters/${CLUSTER}/manifests/${CHART_NAME}/" + + mkdir -p "${OUTPUT_FOLDER}" + cd "${CHART_PATH}" + + helm dependency update --skip-refresh > /dev/null + helm lint --namespace "${CHART_NAME}" --quiet + + local NAMESPACE="${CHART_NAME}" + case "${CHART_NAME}" in + "stack") + NAMESPACE="argocd" + echo "" + echo ">> Special Rendering into 'argocd' namespace ..." + ;; + "cilium" | "coredns" | "metrics-server" | "prometheus-operator-crds") + NAMESPACE="kube-system" + echo "" + echo ">> Special Rendering for ${CHART_NAME} into 'kube-system' namespace ..." + ;; + *) + echo "" + echo ">> Standard Rendering for ${CHART_NAME} ..." + esac + + echo "" + echo ">> Formating rendered template ..." + local TEMPLATE + TEMPLATE=$(helm template "${CHART_NAME}" ./ --namespace "${NAMESPACE}" --include-crds --dry-run=server --api-versions "gateway.networking.k8s.io/v1/HTTPRoute") + + # Format and split rendered template + echo "${TEMPLATE}" | yq '... comments=""' | yq 'select(. != null)' | yq -s '"'"${OUTPUT_FOLDER}"'" + .kind + "-" + .metadata.name + ".yaml"' + + # Strip comments again to ensure formatting correctness + if ls "${OUTPUT_FOLDER}"*.yaml 1> /dev/null 2>&1; then + yq -i '... comments=""' "${OUTPUT_FOLDER}"*.yaml + + fi + + echo "" + echo ">> Manifests for ${CHART_NAME} rendered successfully." + + else + echo "" + echo ">> Directory ${CHART_PATH} does not contain a Chart.yaml. Skipping ..." + + fi + + } + + export -f render_chart + export MAIN_DIR CLUSTER MANIFEST_DIR + + # Run rendering in parallel + for DIR in ${RENDER_DIR}; do + echo "${DIR}" + + done | xargs -n 1 -P 4 -I {} bash -c 'render_chart "$@"' _ {} + + echo "----" + + - name: Check for Changes + id: check-changes + if: steps.check-dir-changes.outputs.changes-detected == 'true' + run: | + cd "${MANIFEST_DIR}" + + GIT_CHANGES=$(git status --porcelain) + + if [ -n "${GIT_CHANGES}" ]; then + echo "" + echo ">> Changes detected" + git status --porcelain + + echo "----" + + echo "changes-detected=true" >> "$GITHUB_OUTPUT" + + else + echo "" + echo ">> No changes detected, skipping PR creation" + + echo "----" + + fi + + - name: Commit and Push Changes + id: commit-push + if: steps.check-changes.outputs.changes-detected == 'true' + env: + BRANCH_NAME: ${{ steps.prepare-manifest-branch.outputs.BRANCH_NAME }} + IS_AUTOMERGE: ${{ steps.mode.outputs.is_automerge }} + run: | + cd "${MANIFEST_DIR}" + + MSG="chore: Update manifests after change" + + if [[ "$IS_AUTOMERGE" == "true" ]]; then + MSG="chore: Update manifests after automerge" + + fi + + echo "" + echo ">> Commiting changes to ${BRANCH_NAME} ..." + git add . + git commit -m "${MSG}" + + REPO_URL="${{ secrets.REPO_URL }}/${{ gitea.repository }}" + + echo "" + echo ">> Pushing changes to ${REPO_URL} ..." + + git push -u "https://oauth2:${{ secrets.BOT_TOKEN }}@${REPO_URL#*://}" "${BRANCH_NAME}" + + echo "----" + + echo "push=true" >> "$GITHUB_OUTPUT" + echo "HEAD_BRANCH=${BRANCH_NAME}" >> "$GITHUB_OUTPUT" + + - name: Check for Pull Request + id: check-for-pull-request + if: steps.commit-push.outputs.push == 'true' && steps.mode.outputs.is_automerge == 'false' + env: + GITEA_TOKEN: ${{ secrets.BOT_TOKEN }} + GITEA_URL: ${{ secrets.REPO_URL }} + HEAD_BRANCH: ${{ steps.commit-push.outputs.HEAD_BRANCH }} + run: | + cd "${MANIFEST_DIR}" + + API_ENDPOINT="${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/pulls?base_branch=${BASE_BRANCH}&state=open&page=1" + + echo "" + echo ">> Checking if PR from branch ${HEAD_BRANCH} into ${BASE_BRANCH}" + echo ">> With Endpoint of:" + echo "$API_ENDPOINT" + + HTTP_STATUS=$(curl -X GET -s -w '%{http_code}' -o response_body.json -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "$API_ENDPOINT") + + if [ "$HTTP_STATUS" == "200" ] && [ "$(cat response_body.json | jq -r .[0].state)" == "open" ]; then + echo "" + echo ">> Pull Request has been found open, will update" + + echo "----" + + echo "pull-request-exists=$(cat response_body.json | jq -r .[0].number)" >> "$GITHUB_OUTPUT" + echo "pull-request-url=$(cat response_body.json | jq -r .[0].html_url)" >> "$GITHUB_OUTPUT" + + else + echo "" + echo ">> Pull Request not found" + + echo "----" + + echo "pull-request-exists=false" >> "$GITHUB_OUTPUT" + + fi + + - name: Create Pull Request + id: create-pull-request + if: steps.commit-push.outputs.push == 'true' && (steps.mode.outputs.is_automerge == 'true' || steps.check-for-pull-request.outputs.pull-request-exists == 'false') + env: + IS_AUTOMERGE: ${{ steps.mode.outputs.is_automerge }} + GITEA_TOKEN: ${{ secrets.BOT_TOKEN }} + GITEA_URL: ${{ secrets.REPO_URL }} + HEAD_BRANCH: ${{ steps.commit-push.outputs.HEAD_BRANCH }} + CHARTS: ${{ steps.check-dir-changes.outputs.render-dir-csv }} + EVENT_NAME: ${{ github.event_name }} + ACTOR: ${{ github.actor }} + SHA: ${{ github.sha }} + REF: ${{ github.ref_name }} + run: | + cd "${MANIFEST_DIR}" + + API_ENDPOINT="${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/pulls" + + BODY=$(printf "This PR contains newly rendered Kubernetes manifests automatically generated by the CI workflow.\n\n### Details\n- **Trigger**: \`%s\` by \`@%s\`\n- **Commit**: \`%s\` (on \`%s\`)\n- **Charts Updated**: \`%s\`" "${EVENT_NAME}" "${ACTOR}" "${SHA:0:7}" "${REF}" "${CHARTS}") + + if [[ "$IS_AUTOMERGE" == "true" ]]; then + TITLE="Automated Manifest Update - Automerge" + BODY=$(printf "%s\n\n_This PR is expected to be automerged._" "${BODY}") + + else + TITLE="Automated Manifest Update" + + fi + + PAYLOAD=$(jq -n --arg head "${HEAD_BRANCH}" --arg base "${BASE_BRANCH}" --arg assignee "${ASSIGNEE}" --arg title "${TITLE}" --arg body "${BODY}" '{head: $head, base: $base, assignee: $assignee, title: $title, body: $body}') + + HTTP_STATUS=$(curl -X POST -s -w '%{http_code}' -o response_body.json --data "$PAYLOAD" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "$API_ENDPOINT") + + if [ "$HTTP_STATUS" == "201" ]; then + echo "" + echo ">> Pull Request created successfully!" + + echo "----" + + echo "pull-request-url=$(jq -r .html_url response_body.json)" >> "$GITHUB_OUTPUT" + echo "pull-request-id=$(jq -r .id response_body.json)" >> "$GITHUB_OUTPUT" + echo "pull-request-number=$(jq -r .number response_body.json)" >> "$GITHUB_OUTPUT" + echo "pull-request-operation=created" >> "$GITHUB_OUTPUT" + + elif [[ "$HTTP_STATUS" == "422" || "$HTTP_STATUS" == "409" ]]; then + echo "" + echo ">> Failed to create PR (Already exists)" + + else + echo "" + echo ">> Failed to create PR, HTTP status code: $HTTP_STATUS"; exit 1 + + fi + + - name: Merge Changes + id: merge-changes + if: steps.commit-push.outputs.push == 'true' && steps.mode.outputs.is_automerge == 'true' + env: + GITEA_TOKEN: ${{ secrets.BOT_TOKEN }} + GITEA_URL: ${{ secrets.REPO_URL }} + PR_NUMBER: ${{ steps.create-pull-request.outputs.pull-request-number }} + run: | + cd "${MANIFEST_DIR}" + + API_ENDPOINT="${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/pulls/${PR_NUMBER}/merge" + + PAYLOAD=$(jq -n --arg Do "merge" '{Do: $Do}') + + HTTP_STATUS=$(curl -X POST -s -w '%{http_code}' -o response_body.json --data "$PAYLOAD" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "$API_ENDPOINT") + + if [ "$HTTP_STATUS" == "200" ]; then + echo "" + echo ">> Pull Request merged successfully!" + + echo "----" + + echo "pull-request-operation=merged" >> "$GITHUB_OUTPUT" + + else + echo "" + echo ">> Failed to merge PR, HTTP status code: $HTTP_STATUS"; exit 1 + + fi + + - name: Cleanup Branch + if: failure() && steps.mode.outputs.is_automerge == 'true' + env: + BRANCH_NAME: ${{ steps.prepare-manifest-branch.outputs.BRANCH_NAME }} + run: | + cd "${MANIFEST_DIR}" + + echo "" + echo ">> Removing branch: ${BRANCH_NAME}" + git push origin --delete "${BRANCH_NAME}" || true + + echo "----" + + - name: ntfy Created + uses: niniyas/ntfy-action@master + if: steps.create-pull-request.outputs.pull-request-operation == 'created' && steps.mode.outputs.is_automerge == 'false' + with: + url: "${{ secrets.NTFY_URL }}" + topic: "${{ secrets.NTFY_TOPIC }}" + title: "Manifest Render - Open PR" + priority: 3 + headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}' + tags: action,successfully,completed + details: "Created renderd manifests for cluster '${CLUSTER}' with charts: ${{ steps.check-dir-changes.outputs.render-dir-csv }}" + icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png" + actions: '[{"action": "view", "label": "Open Gitea", "url": "${{ steps.create-pull-request.outputs.pull-request-url }}", "clear": true}]' + + - name: ntfy Updated + uses: niniyas/ntfy-action@master + if: steps.commit-push.outputs.push == 'true' && steps.check-for-pull-request.outputs.pull-request-exists != 'false' && steps.mode.outputs.is_automerge == 'false' + with: + url: "${{ secrets.NTFY_URL }}" + topic: "${{ secrets.NTFY_TOPIC }}" + title: "Manifest Render - PR Updated" + priority: 3 + headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}' + tags: action,successfully,completed + details: "Updated rendered manifests PR for cluster '${CLUSTER}' with charts: ${{ steps.check-dir-changes.outputs.render-dir-csv }}" + icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png" + actions: '[{"action": "view", "label": "Open Gitea", "url": "${{ steps.check-for-pull-request.outputs.pull-request-url }}", "clear": true}]' + + - name: ntfy Merged + uses: niniyas/ntfy-action@master + if: steps.merge-changes.outputs.pull-request-operation == 'merged' + with: + url: "${{ secrets.NTFY_URL }}" + topic: "${{ secrets.NTFY_TOPIC }}" + title: "Manifest Render - Automerged" + priority: 3 + headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}' + tags: action,successfully,completed + details: "Automerged manifest rendering for cluster '${CLUSTER}' with charts: ${{ steps.check-dir-changes.outputs.render-dir-csv }}" + icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png" + actions: '[{"action": "view", "label": "Open Gitea", "url": "${{ steps.create-pull-request.outputs.pull-request-url }}", "clear": true}]' + + - name: ntfy Failed + uses: niniyas/ntfy-action@master + if: failure() + with: + url: "${{ secrets.NTFY_URL }}" + topic: "${{ secrets.NTFY_TOPIC }}" + title: "Manifest Render Failure" + priority: 4 + headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}' + tags: action,failed + details: "Manifest rendering for Infrastructure has failed!" + icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png" + actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/infrastructure/actions?workflow=render-manifests.yaml", "clear": true}]' + image: true