diff --git a/.github/workflows/add-pr-to-devops.yml b/.github/workflows/add-pr-to-devops.yml new file mode 100644 index 0000000..08b6733 --- /dev/null +++ b/.github/workflows/add-pr-to-devops.yml @@ -0,0 +1,25 @@ +name: Add PR to DevOps Board + +on: + pull_request: + types: [opened, reopened] + branches: [main, master] + +jobs: + add_to_project: + runs-on: ubuntu-latest + if: | + github.event.pull_request.base.ref == 'main' || + github.event.pull_request.base.ref == 'master' + permissions: + contents: read + pull-requests: write + repository-projects: write + organization-projects: write + steps: + - name: Add PR to DevOps Release Board + uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/dhwani-ris/projects/## + github-token: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/auto-reviewer.yml b/.github/workflows/auto-reviewer.yml index 5ba1e01..f97e436 100644 --- a/.github/workflows/auto-reviewer.yml +++ b/.github/workflows/auto-reviewer.yml @@ -2,7 +2,8 @@ name: Auto Request Review on: pull_request: - types: [opened, synchronize, reopened, ready_for_review] + types: [opened, synchronize, reopened, ready_for_review, closed] + branches: [master] permissions: pull-requests: write @@ -13,8 +14,8 @@ jobs: name: Request Review from Default Reviewer runs-on: ubuntu-latest if: | - github.event.pull_request.base.ref == 'main' || - github.event.pull_request.base.ref == 'master' + (github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize' || github.event.action == 'ready_for_review') && + (github.event.pull_request.base.ref == 'main' || github.event.pull_request.base.ref == 'master') steps: - name: Request review from default reviewer diff --git a/.github/workflows/bot-handler.yml b/.github/workflows/bot-handler.yml index 8b0476a..050c0ed 100644 --- a/.github/workflows/bot-handler.yml +++ b/.github/workflows/bot-handler.yml @@ -4,7 +4,8 @@ on: issue_comment: types: [created, edited] pull_request: - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, closed] + branches: [master] permissions: contents: write diff --git a/.github/workflows/devops-checklist-submit.yml b/.github/workflows/devops-checklist-submit.yml new file mode 100644 index 0000000..0630c26 --- /dev/null +++ b/.github/workflows/devops-checklist-submit.yml @@ -0,0 +1,327 @@ +name: DevOps Checklist Submission + +on: + issue_comment: + types: [created, edited] + pull_request: + types: [synchronize, reopened, ready_for_review, closed] + +permissions: + pull-requests: write + contents: read + +jobs: + submit-checklist: + runs-on: ubuntu-latest + if: | + (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '/submit-checklist')) || + (github.event_name == 'pull_request' && (github.event.action == 'synchronize' || github.event.action == 'reopened' || github.event.action == 'ready_for_review' || github.event.action == 'closed')) + steps: + - name: Get PR number + id: get-pr + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prNumber = context.payload.issue.number; + core.setOutput('pr_number', prNumber); + return prNumber; + + - name: Submit and lock checklist + if: github.event_name == 'issue_comment' && contains(github.event.comment.body, '/submit-checklist') + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prNumber = context.payload.issue.number; + const submitter = context.payload.comment.user.login; + const submitTime = new Date().toISOString(); + const submitDate = new Date().toLocaleString('en-US', { + timeZone: 'UTC', + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short' + }); + + // Get all comments to find the checklist + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + + const checklistComment = comments.data.find( + comment => comment.user.type === 'Bot' && + comment.body.includes('DevOps Checklist - Workflow Review') + ); + + if (!checklistComment) { + console.log('Checklist comment not found'); + return; + } + + // Check if already submitted + if (checklistComment.body.includes('✅ **CHECKLIST SUBMITTED**')) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `⚠️ This checklist has already been submitted and cannot be modified.` + }); + return; + } + + // Extract the current checklist content (preserve checkboxes) + let checklistBody = checklistComment.body; + + // Extract submission info to preserve it + const submissionInfo = `**Submitted by:** @${submitter} + **Submitted at:** ${submitDate} (UTC)`; + + // Replace the submission section with submitted status + const submittedSection = `--- + + ### ✅ **CHECKLIST SUBMITTED** + + ${submissionInfo} + + 🔒 **This checklist is now locked and cannot be modified.** + + + + --- + **Note:** This checklist was submitted and is final. No further changes can be made.`; + + // Find and replace the submission section + // Look for the "Submit Checklist" section and replace everything from there to the end + const submitSectionStart = checklistBody.indexOf('### 📤 Submit Checklist'); + if (submitSectionStart !== -1) { + // Keep everything before the submission section, then add the submitted section + checklistBody = checklistBody.substring(0, submitSectionStart) + submittedSection; + } else { + // If section not found, append the submitted section + checklistBody = checklistBody + '\n\n' + submittedSection; + } + + // Extract and store the exact checkbox states before locking + const checkboxStates = []; + const checkboxRegex = /- \[([ x])\] (.+)/g; + let match; + while ((match = checkboxRegex.exec(checklistBody)) !== null) { + checkboxStates.push({ + checked: match[1] === 'x', + text: match[2] + }); + } + + // Store checkbox states in a hidden comment for restoration + const checkboxStatesJson = JSON.stringify(checkboxStates); + + // Update the checklist comment to show it's submitted + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: checklistComment.id, + body: checklistBody + }); + + // Add a hidden comment with checkbox states for restoration + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `` + }); + + // Add label to track submission + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + labels: ['devops-checklist-submitted'] + }); + } catch (e) { + // Label might not exist, create it + try { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'devops-checklist-submitted', + color: '28a745', + description: 'DevOps Checklist has been submitted and locked' + }); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + labels: ['devops-checklist-submitted'] + }); + } catch (labelError) { + console.log('Could not create/add label:', labelError.message); + } + } + + // Add a confirmation comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `✅ **Checklist submitted successfully!** + + The DevOps Checklist has been locked and cannot be modified. + + **Submitted by:** @${submitter} + **Time:** ${submitDate} (UTC)` + }); + + console.log(`Checklist submitted by ${submitter} at ${submitTime}`); + + - name: Re-lock checklist if edited after submission + if: | + (github.event_name == 'issue_comment' && github.event.action == 'edited') || + (github.event_name == 'pull_request') + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prNumber = github.event_name == 'issue_comment' + ? context.payload.issue.number + : context.payload.pull_request.number; + + // Get all comments to find the checklist + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + + const checklistComment = comments.data.find( + comment => comment.user.type === 'Bot' && + comment.body.includes('DevOps Checklist - Workflow Review') + ); + + if (!checklistComment) { + return; + } + + // Check if checklist was previously submitted (look for submission marker) + const hasSubmissionMarker = checklistComment.body.includes('CHECKLIST_SUBMITTED_LOCK'); + const hasSubmittedText = checklistComment.body.includes('✅ **CHECKLIST SUBMITTED**'); + + // If it was submitted, ensure it's locked and restore checkbox states + if (hasSubmissionMarker || hasSubmittedText) { + let checklistBody = checklistComment.body; + + // Get stored checkbox states from hidden comment + let storedStates = null; + const allComments = comments.data; + for (const comment of allComments) { + if (comment.body.includes('CHECKLIST_STATES:')) { + const statesMatch = comment.body.match(/CHECKLIST_STATES:(.+)/); + if (statesMatch) { + try { + storedStates = JSON.parse(statesMatch[1]); + break; + } catch (e) { + console.log('Could not parse stored states'); + } + } + } + } + + // Extract submission info + const submitterMatch = checklistBody.match(/\*\*Submitted by:\*\* @(\S+)/); + const dateMatch = checklistBody.match(/\*\*Submitted at:\*\* (.+?)(?:\n|UTC)/); + const submitter = submitterMatch ? submitterMatch[1] : 'System'; + const submitDate = dateMatch ? dateMatch[1].trim() : new Date().toLocaleString('en-US', { + timeZone: 'UTC', + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short' + }); + + // Restore checkbox states if we have stored states + if (storedStates && storedStates.length > 0) { + let restoredBody = checklistBody; + const checkboxRegex = /- \[([ x])\] (.+)/g; + let stateIndex = 0; + + restoredBody = restoredBody.replace(checkboxRegex, (match, checked, text) => { + if (stateIndex < storedStates.length) { + const state = storedStates[stateIndex]; + stateIndex++; + // Restore the original checkbox state + return `- [${state.checked ? 'x' : ' '}] ${state.text}`; + } + return match; + }); + + checklistBody = restoredBody; + } + + // Check if lock section is present and complete + const hasLockMessage = checklistBody.includes('🔒 **This checklist is now locked and cannot be modified.**'); + const hasCompleteLock = hasSubmittedText && hasLockMessage; + + // If lock is missing or incomplete, or checkboxes were changed, restore it + if (!hasCompleteLock || (storedStates && checklistBody !== checklistComment.body)) { + // Find where to insert/update the lock section + const submitIndex = checklistBody.indexOf('### ✅ **CHECKLIST SUBMITTED**'); + const lockSection = `--- + + ### ✅ **CHECKLIST SUBMITTED** + + **Submitted by:** @${submitter} + **Submitted at:** ${submitDate} (UTC) + + 🔒 **This checklist is now locked and cannot be modified.** + + + + --- + **Note:** This checklist was submitted and is final. No further changes can be made.`; + + if (submitIndex !== -1) { + // Replace from submission header to end of comment + const beforeSubmit = checklistBody.substring(0, submitIndex); + checklistBody = beforeSubmit + lockSection; + } else { + // Append lock section if submission header is missing + checklistBody = checklistBody + '\n\n' + lockSection; + } + + // Re-lock the checklist with restored states + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: checklistComment.id, + body: checklistBody + }); + + // Add a warning comment only if this is an edit event (not PR sync) + if (github.event_name == 'issue_comment' && github.event.action == 'edited') { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `⚠️ **Checklist Re-locked** + + The DevOps Checklist was modified after submission. It has been automatically re-locked and restored to its submitted state. + + 🔒 **This checklist cannot be modified once submitted.**` + }); + } + + console.log('Checklist re-locked after unauthorized edit'); + } + } + diff --git a/.github/workflows/devops-checklist.yml b/.github/workflows/devops-checklist.yml index 1bc8956..f65a085 100644 --- a/.github/workflows/devops-checklist.yml +++ b/.github/workflows/devops-checklist.yml @@ -137,22 +137,22 @@ jobs: const fixes = commits.filter(c => c.message.startsWith('fix')).map(c => c.message.replace(/^fix(\(.+?\))?:\s*/i, '')); const other = commits.filter(c => !c.message.startsWith('feat') && !c.message.startsWith('fix') && !c.message.startsWith('chore') && !c.message.startsWith('ci')); - // Build feature details + // Build feature details (without numbering - will be added in formatFeatureDetails) let featureDetails = []; if (features.length > 0) { - featureDetails.push(...features.map(f => `1) ${f}`)); + featureDetails.push(...features); } if (fixes.length > 0) { - featureDetails.push(...fixes.map(f => `2) ${f}`)); + featureDetails.push(...fixes); } if (other.length > 0) { - featureDetails.push(...other.slice(0, 5).map((o, i) => `${i + 3}) ${o.message}`)); + featureDetails.push(...other.slice(0, 5).map(o => o.message)); } const today = new Date().toISOString().split('T')[0]; const releaseDate = today.split('-').reverse().join('-'); // Format: DD-MM-YYYY - // Format feature details better + // Format feature details with sequential numbering const formatFeatureDetails = (details) => { if (details.length === 0) return 'See commits above'; return details.map((f, i) => `${i + 1}) ${f}`).join('
'); @@ -181,87 +181,6 @@ jobs: |-------|-----------------|----------------|-----------------| | 1. | \`${context.repo.repo}\` | \`${pr.base.ref}-release-${version}\` | ${formatFeatureDetails(featureDetails)} | - **Dependencies:** - - Dependencies updated: \`TBD\` *(Please review and update)* - \`\`\` - - \`\`\` - - **Database Changes (Queries to run):** - - Database changes required: \`TBD\` *(Please review and update)* - \`\`\` - - \`\`\` - - **Testing:** - - [ ] Unit tests passed - - [ ] Integration tests passed - - [ ] E2E tests passed - - [ ] Manual testing completed - \`\`\` - - \`\`\` - - **Known Issues:** - - Known issues: \`TBD\` *(Please review and update)* - \`\`\` - - \`\`\` - - **Contact Information:** - - Support Team Email: \`\`\`\`\`\` - - Support Team Phone: \`\`\`\`\`\` - - **Attachments:** - - Deployment files attached/committed: \`TBD\` *(Please review and update)* - \`\`\` - - \`\`\` - - --- - - ### For DevOps Team Use Only - *(To be filled by the DevOps team after deploying the release)* - - **Deployment Details:** - - Date and time of deployment: \`\`\`\`\`\` - - Deployed by: \`\`\`\`\`\` - - Deployment Status: \`\`\`\`\`\` - - **Deployment Instructions:** - - [ ] Pre-deployment tasks completed (backups, etc.) - - [ ] Production environment accessed securely - - [ ] Latest release pulled from version control - - [ ] Dependencies installed/updated - - [ ] Database migrations run (if applicable) - - [ ] Application services restarted - - [ ] Deployment monitored and verified - - **Rollback Plan:** - - [ ] Rollback procedure documented - - [ ] Previous version tag identified: \`\`\`\`\`\` - - [ ] Database rollback scripts prepared (if applicable) - - [ ] Rollback tested in staging environment - - **Post-Deployment Checklist:** - - [ ] Service availability and response times verified - - [ ] System resources monitored - - [ ] Critical user scenarios tested - - [ ] Data integrity confirmed - - [ ] Error logs reviewed - - [ ] Security scans completed - - [ ] Server and infrastructure health checked - - [ ] Backup and disaster recovery procedures validated - - **Notes:** - \`\`\` - - \`\`\` - - **Acknowledgment:** - - [ ] Deployment acknowledged and system ready for production use - - --- **Note:** This deployment document was **automatically generated** from PR commits and information. Please review and update the TBD sections before merging.`; // Check if comment already exists @@ -320,35 +239,6 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const pr = context.payload.pull_request; - const checklist = `## 🔧 DevOps Checklist - Workflow Review - - **Please review all workflows and checks before merging:** - - ### Workflow Status Review - - [ ] All CI/CD workflows are passing - - [ ] Quality Checks workflow passed - - [ ] Security Scan workflow passed - - [ ] Code quality checks passed - - [ ] Test coverage meets requirements - - ### Review Status - - [ ] All required reviewers have approved - - [ ] Code review completed - - [ ] Security review completed (if applicable) - - ### Pre-Merge Verification - - [ ] Deployment Notes document reviewed (see Deployment Notes comment above) - - [ ] All commits reviewed - - [ ] Breaking changes identified (if any) - - [ ] Version number verified (if applicable) - - ### Final Checks - - [ ] No blocking issues or errors - - [ ] Ready for production deployment - - [ ] Rollback plan understood (if high-risk) - - --- - **Note:** This checklist is for DevOps team to verify all workflows and checks before merging.`; // Check if comment already exists const comments = await github.rest.issues.listComments({ @@ -363,22 +253,62 @@ jobs: ); if (existingComment) { - // Update existing comment - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - body: checklist - }); - console.log('Updated existing DevOps Checklist comment'); - } else { - // Create new comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: checklist - }); - console.log('Created new DevOps Checklist comment'); + // Check if already submitted (check both visible marker and hidden marker) + if (existingComment.body.includes('✅ **CHECKLIST SUBMITTED**') || + existingComment.body.includes('CHECKLIST_SUBMITTED_LOCK')) { + console.log('Checklist already submitted and locked, cannot update'); + return; + } + // Don't update existing comment to preserve checkbox states + console.log('DevOps Checklist comment already exists, preserving user checkboxes'); + return; } + + // Only create new comment if it doesn't exist + const checklist = `## 🔧 DevOps Checklist + + ### Workflows & Quality + - [ ] All CI/CD workflows passing + - [ ] Code quality checks passed + - [ ] Security scans passed + - [ ] No secrets or credentials exposed + + ### Review & Testing + - [ ] Code review approved + - [ ] Tested on staging/UAT + - [ ] No critical bugs pending + + ### Documentation + - [ ] Deployment notes reviewed + - [ ] Rollback plan documented + - [ ] 🔗 [Rollback Guidelines](https://devops.dhwaniris.com/rollback-guidelines) + + ### Deployment Readiness + - [ ] DB migrations ready (if applicable) + - [ ] Config changes documented + - [ ] Monitoring alerts validated + + ### Final Decision + - [ ] **GO** - Ready for production + - [ ] **NO-GO** - Blocked (see notes) + + **Notes:** + \`\`\` + + \`\`\` + + --- + + **Submit:** Comment \`/submit-checklist\` to lock this checklist + + ⚠️ Once submitted, this checklist cannot be modified.`; + + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: checklist + }); + console.log('Created new DevOps Checklist comment');