From 6c4df85cc0f5c8983b8fc09bb749235685f1a7f5 Mon Sep 17 00:00:00 2001 From: Alex Lebens Date: Tue, 2 Dec 2025 20:47:43 -0600 Subject: [PATCH] add automerge --- .../workflows/render-manfiest-automerge.yaml | 378 ++++++++++++++++++ .gitea/workflows/render-manifests.yaml | 13 +- 2 files changed, 381 insertions(+), 10 deletions(-) create mode 100644 .gitea/workflows/render-manfiest-automerge.yaml diff --git a/.gitea/workflows/render-manfiest-automerge.yaml b/.gitea/workflows/render-manfiest-automerge.yaml new file mode 100644 index 000000000..36c34796d --- /dev/null +++ b/.gitea/workflows/render-manfiest-automerge.yaml @@ -0,0 +1,378 @@ +name: render-manfiest-automerge + +on: + pull_request: + branches: + - main + paths: + - 'clusters/cl01tl/helm/**' + types: + - labeled + +env: + CLUSTER: cl01tl + BASE_BRANCH: manifests + BRANCH_NAME_BASE: auto/update-manifests-automerge + MAIN_DIR: /workspace/alexlebens/infrastructure/infrastructure + MANIFEST_DIR: /workspace/alexlebens/infrastructure/infrastructure-manifests + +jobs: + render-manfiest-automerge: + runs-on: ubuntu-js + if: | + (github.event_name == 'pull_request' && github.event.action == 'labeled' && contains(github.event.pull_request.labels.*.name, 'automerge')) + 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 + + - name: Prepare Manifest Branch + id: prepare-manifest-branch + run: | + cd ${MANIFEST_DIR} + + BRANCH_NAME="${BRANCH_NAME_BASE}-$(date +%Y%m%d%H%M%S)" + + echo ">> Configure git to use gitea-bot as user ..." + git config user.name "gitea-bot" + git config user.email "gitea-bot@alexlebens.net" + + echo ">> Creating branch ..." + git checkout -b $BRANCH_NAME + + echo "----" + + echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITEA_OUTPUT + + - name: Check which Directories have Changes + id: check-dir-changes + run: | + cd ${MAIN_DIR} + + RENDER_DIR=() + + echo ">> Checking for changes ..." + GIT_DIFF=$(git diff --name-only ${{ gitea.event.before }}..HEAD | xargs -I {} dirname {} | sort -u | grep "clusters/cl01tl/helm/") + + if [ -n $GIT_DIFF ]; then + echo ">> Changes detected:" + echo "$GIT_DIFF" + for path in $GIT_DIFF; do + RENDER_DIR+=$(echo "$path" | awk -F '/' '{print $4}') + done + + else + echo ">> No changes detected" + + fi + + echo ">> Directories to Render:" + echo "$(printf "%s\n" "${RENDER_DIR[@]}" | sort -u)" + + echo "----" + + echo "changes-detected=true" >> $GITEA_OUTPUT + echo "render-dir<> $GITEA_OUTPUT + echo "$(printf "%s\n" "${RENDER_DIR[@]}" | sort -u)" >> $GITEA_OUTPUT + echo "EOF" >> $GITEA_OUTPUT + + - 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 ">> 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 +2 | head -n -1 \ + | awk '{ print "helm repo add " $1 " " $3 }' \ + | while read cmd; do echo "$cmd" | sh; done || true + done + + 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 ">> Remove manfiest files and rebuild from source ..." + + for dir in ${RENDER_DIR}; do + chart_path=${MANIFEST_DIR}/clusters/${CLUSTER}/manifests/$dir + + 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 ">> Rendering Manifests ..." + + for dir in ${RENDER_DIR}; do + chart_path=${MAIN_DIR}/clusters/${CLUSTER}/helm/$dir + chart_name=$(basename "$chart_path") + + echo ">> Rendering chart: $chart_name" + echo ">> Chart path $chart_path" + + if [ -f "$chart_path/Chart.yaml" ]; then + mkdir -p ${MANIFEST_DIR}/clusters/${CLUSTER}/manifests/$chart_name + OUTPUT_FILE="${MANIFEST_DIR}/clusters/${CLUSTER}/manifests/$chart_name/$chart_name.yaml" + + cd $chart_path + + echo "" + echo ">> Building helm dependency ..." + helm dependency build + + echo "" + echo ">> Linting helm ..." + helm lint --namespace "$chart_name" + + echo "" + echo ">> Rendering templates ..." + helm template "$chart_name" ./ --namespace "$chart_name" --include-crds > "$OUTPUT_FILE" + + echo "" + echo ">> Manifests for $chart_name rendered to $OUTPUT_FILE" + echo "" + else + echo "" + echo ">> Directory $chart_path does not contain a Chart.yaml. Skipping ..." + echo "" + fi + done + + echo "----" + + - name: Check for Changes + id: check-changes + if: steps.check-dir-changes.outputs.changes-detected == 'true' + run: | + cd ${MANIFEST_DIR} + + if git status --porcelain | grep -q .; then + echo ">> Changes detected" + git status --porcelain + echo "changes-detected=true" >> $GITEA_OUTPUT + else + echo ">> No changes detected, skipping PR creation" + exit 0 + fi + + echo "----" + + - 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 }} + run: | + cd ${MANIFEST_DIR} + + echo ">> Commiting changes to ${BRANCH_NAME} ..." + git add . + git commit -m "chore: Update manifests after automerge" + + REPO_URL="${{ secrets.REPO_URL }}/${{ gitea.repository }}" + echo ">> Pushing changes to $REPO_URL ..." + git push -u "https://oauth2:${{ secrets.BOT_TOKEN }}@$(echo $REPO_URL | sed -e 's|https://||')" ${BRANCH_NAME} + + echo "----" + + echo "push=true" >> $GITEA_OUTPUT + + - name: Create Pull Request + id: create-pull-request + if: steps.commit-push.outputs.push == 'true' + env: + GITEA_TOKEN: ${{ secrets.BOT_TOKEN }} + GITEA_URL: ${{ secrets.REPO_URL }} + BRANCH_NAME: ${{ steps.prepare-manifest-branch.outputs.BRANCH_NAME }} + run: | + cd ${MANIFEST_DIR} + + API_ENDPOINT="${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/pulls" + + PAYLOAD=$( jq -n \ + --arg head "${BRANCH_NAME}" \ + --arg base "${BASE_BRANCH}" \ + --arg title "Automated Manifest Update" \ + --arg body "This PR contains newly rendered Kubernetes manifests automatically generated by the CI workflow. This is expected to be automerged." \ + '{head: $head, base: $base, title: $title, body: $body'} ) + + echo ">> Creating PR from branch ${BRANCH_NAME} into ${BASE_BRANCH}" + echo ">> With Endpoint of:" + echo "$API_ENDPOINT" + echo ">> With Payload of:" + echo "$PAYLOAD" + + HTTP_STATUS=$( + curl -X POST \ + --silent \ + --write-out '%{http_code}' \ + --output response_body.json \ + --dump-header response_headers.txt \ + --data "$PAYLOAD" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + "$API_ENDPOINT" 2> response_errors.txt + ) + + echo ">> HTTP Status Code: $HTTP_STATUS" + echo ">> Response Output ..." + echo "----" + cat response_body.json + echo "----" + cat response_headers.txt + echo "----" + cat response_errors.txt + echo "----" + + if [ "$HTTP_STATUS" == "201" ]; then + echo ">> Pull Request created successfully!" + PR_URL=$(cat response_body.json | jq -r .html_url) + echo "pull-request-url=${PR_URL}" >> $GITEA_OUTPUT + PR_ID=$(cat response_body.json | jq -r .id) + echo "pull-request-id=${PR_ID}" >> $GITEA_OUTPUT + echo "pull-request-operation=created" >> $GITEA_OUTPUT + + elif [ "$HTTP_STATUS" == "422" ]; then + echo ">> Failed to create PR (HTTP 422: Unprocessable Entity), PR may already exist" + + elif [ "$HTTP_STATUS" == "409" ]; then + echo ">> Failed to create PR (HTTP 409: Conflict), PR already exists" + + else + echo ">> Failed to create PR, HTTP status code: $HTTP_STATUS" + exit 1 + fi + + echo "----" + + - name: Merge Changes + id: merge-changes + if: steps.commit-push.outputs.push == 'true' + env: + GITEA_TOKEN: ${{ secrets.BOT_TOKEN }} + GITEA_URL: ${{ secrets.REPO_URL }} + BRANCH_NAME: ${{ steps.prepare-manifest-branch.outputs.BRANCH_NAME }} + PR_ID: ${{ steps.prepare-manifest-branch.outputs.pull-request-id }} + run: | + cd ${MANIFEST_DIR} + + API_ENDPOINT="${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/pulls/${PR_ID}/merge" + + PAYLOAD=$( jq -n \ + --arg Do "merge" \ + --arg delete_branch_after_merge "true" \ + '{Do: $Do, delete_branch_after_merge: $delete_branch_after_merge'} ) + + echo ">> Merging PR with ID: ${PR_ID}" + echo ">> With Endpoint of:" + echo "$API_ENDPOINT" + echo ">> With Payload of:" + echo "$PAYLOAD" + + HTTP_STATUS=$( + curl -X POST \ + --silent \ + --write-out '%{http_code}' \ + --output response_body.json \ + --dump-header response_headers.txt \ + --data "$PAYLOAD" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + "$API_ENDPOINT" 2> response_errors.txt + ) + + echo ">> HTTP Status Code: $HTTP_STATUS" + echo ">> Response Output ..." + echo "----" + cat response_body.json + echo "----" + cat response_headers.txt + echo "----" + cat response_errors.txt + echo "----" + + if [ "$HTTP_STATUS" == "200" ]; then + echo ">> Pull Request merged successfully!" + echo "pull-request-operation=merged" >> $GITEA_OUTPUT + + else + echo ">> Failed to create PR, HTTP status code: $HTTP_STATUS" + echo "pull-request-operation=failed" >> $GITEA_OUTPUT + exit 1 + fi + + echo "----" + + - name: Cleanup Branch + if: failure() + env: + BRANCH_NAME: ${{ steps.prepare-manifest-branch.outputs.BRANCH_NAME }} + run: | + cd ${MANIFEST_DIR} + + echo ">> Removing branch: ${BRANCH_NAME}" + git push origin --delete ${BRANCH_NAME} + + echo "----" + + - 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 PR Merged - Infrastructure" + priority: 3 + headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}' + tags: action,successfully,completed + details: "Automerge Manifest rendering for Infrastructure!" + 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}]' + image: true + + - name: ntfy Failed + uses: niniyas/ntfy-action@master + if: failure() + with: + url: "${{ secrets.NTFY_URL }}" + topic: "${{ secrets.NTFY_TOPIC }}" + title: "Manifest Render Failure - Infrastructure" + priority: 4 + headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}' + tags: action,failed + details: "Automerge 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-automerge.yaml", "clear": true}]' + image: true diff --git a/.gitea/workflows/render-manifests.yaml b/.gitea/workflows/render-manifests.yaml index 0e71c5a8f..57f97d781 100644 --- a/.gitea/workflows/render-manifests.yaml +++ b/.gitea/workflows/render-manifests.yaml @@ -323,6 +323,8 @@ jobs: echo ">> Pull Request created successfully!" PR_URL=$(cat response_body.json | jq -r .html_url) echo "pull-request-url=${PR_URL}" >> $GITEA_OUTPUT + PR_ID=$(cat response_body.json | jq -r .id) + echo "pull-request-id=${PR_ID}" >> $GITEA_OUTPUT echo "pull-request-operation=created" >> $GITEA_OUTPUT elif [ "$HTTP_STATUS" == "422" ]; then @@ -338,16 +340,6 @@ jobs: echo "----" - - name: Cleanup Branch - if: failure() && steps.create-pull-request.outcome == 'failure' - env: - HEAD_BRANCH: ${{ steps.commit-push.outputs.HEAD_BRANCH }} - run: | - echo ">> Removing branch: ${HEAD_BRANCH}" - git push origin --delete ${HEAD_BRANCH} - - echo "----" - - name: ntfy Created uses: niniyas/ntfy-action@master if: steps.create-pull-request.outputs.pull-request-operation == 'created' @@ -361,6 +353,7 @@ jobs: details: "Manifest rendering for Infrastructure has created a new Pull Request!" 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}]' + image: true - name: ntfy Failed uses: niniyas/ntfy-action@master