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