An error message is added if user enters wrong credentials while login and the UI of /accounts/confirm-email page is fixed #15157
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD Optimized | |
| # SECURITY NOTE: | |
| # - pull_request: Used for test/docker-test jobs that don't need write permissions | |
| # - pull_request_target: Used for pre-commit job to enable commenting/labeling on forked PRs | |
| # When using pull_request_target, we explicitly checkout the PR head to run checks on the actual PR code | |
| # WARNING: This allows execution of pre-commit hooks from forked PRs. The hooks are defined in | |
| # .pre-commit-config.yaml which could be modified by the fork. However, this is necessary to | |
| # validate PR code and is consistent with the repository's pre-commit-fix.yaml workflow. | |
| # Maintainers should review PRs for malicious pre-commit hook modifications. | |
| on: | |
| #merge_group: | |
| pull_request_target: | |
| types: | |
| - opened | |
| - synchronize | |
| - reopened | |
| - ready_for_review | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| workflow_run: | |
| workflows: ["Pre-commit fix"] | |
| types: | |
| - completed | |
| env: | |
| FORCE_COLOR: 1 | |
| POETRY_CACHE_DIR: ~/.cache/pypoetry | |
| # Default permissions for the workflow | |
| permissions: | |
| contents: read | |
| concurrency: | |
| cancel-in-progress: false | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| jobs: | |
| setup: | |
| name: Setup and Cache Dependencies | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read # Minimal permission for checking out code | |
| outputs: | |
| python-cache-dir: ${{ steps.poetry-cache.outputs.dir }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Cache pre-commit hooks | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pre-commit | |
| key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pre-commit- | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: 3.11.2 | |
| - name: Get Poetry cache directory | |
| id: poetry-cache | |
| run: echo "POETRY_CACHE_DIR=$(poetry config cache-dir)" >> $GITHUB_ENV | |
| - name: Cache Poetry dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cache/pypoetry | |
| ~/.cache/pip | |
| .venv | |
| key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}-${{ hashFiles('**/pyproject.toml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}- | |
| ${{ runner.os }}-poetry- | |
| - name: Print memory usage | |
| run: free -h | |
| pre-commit: | |
| name: Run pre-commit | |
| # Run on pull_request_target (for write permissions to comment/label), push to main, or manual trigger | |
| needs: setup | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| contents: write | |
| actions: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # For pull_request_target, explicitly checkout the PR head from the fork | |
| # For pull_request, the default behavior already checks out the PR head | |
| ref: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.pull_request.head.sha || github.ref }} | |
| # For forked PRs with pull_request_target, checkout from the fork repository | |
| # SECURITY: We run pre-commit which only executes linters/formatters, not arbitrary code from the PR | |
| repository: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name || github.repository }} | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: 3.11.2 | |
| - name: Run pre-commit | |
| id: pre-commit | |
| run: | | |
| pip install pre-commit | |
| set +e | |
| pre-commit run --all-files > pre-commit-output.txt 2>&1 | |
| echo "exit_code=$?" >> $GITHUB_OUTPUT | |
| set -e | |
| continue-on-error: true | |
| - name: Comment on PR if pre-commit fails | |
| if: github.event_name == 'pull_request_target' && steps.pre-commit.outputs.exit_code != '0' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const precommitOutput = fs.readFileSync('pre-commit-output.txt', 'utf8'); | |
| // Get last 100 lines of output | |
| const lines = precommitOutput.split('\n'); | |
| const last100Lines = lines.slice(-100).join('\n'); | |
| const truncated = lines.length > 100; | |
| const message = `## ❌ Pre-commit checks failed | |
| The pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them: | |
| \`\`\`bash | |
| # Install pre-commit if you haven't already | |
| pip install pre-commit | |
| # Run pre-commit on all files | |
| pre-commit run --all-files | |
| # Or run pre-commit on staged files only | |
| pre-commit run | |
| \`\`\` | |
| After running these commands, the pre-commit hooks will automatically fix most issues. | |
| Please review the changes, commit them, and push to your branch. | |
| 💡 **Tip**: You can set up pre-commit to run automatically on every commit by running: | |
| \`\`\`bash | |
| pre-commit install | |
| \`\`\` | |
| <details> | |
| <summary>Pre-commit output${truncated ? ' (last 100 lines)' : ''}</summary> | |
| \`\`\` | |
| ${last100Lines} | |
| \`\`\` | |
| </details> | |
| For more information, see the [pre-commit documentation](https://pre-commit.com/).`; | |
| try { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: message | |
| }); | |
| } catch (error) { | |
| if (error.status === 403) { | |
| core.warning('Permission denied: Cannot comment on PR. Repository may need to enable "Allow GitHub Actions to create and approve pull requests" in Settings > Actions > General.'); | |
| } else { | |
| throw error; | |
| } | |
| } | |
| - name: Add pre-commit status label | |
| if: github.event_name == 'pull_request_target' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const pull_number = context.issue.number; | |
| // Determine label based on pre-commit exit code | |
| const preCommitExitCode = '${{ steps.pre-commit.outputs.exit_code }}'; | |
| const newLabel = preCommitExitCode === '0' ? 'pre-commit: passed' : 'pre-commit: failed'; | |
| const labelColor = preCommitExitCode === '0' ? '0e8a16' : 'e74c3c'; // Green or Red | |
| const description = preCommitExitCode === '0' ? 'Pre-commit checks passed' : 'Pre-commit checks failed'; | |
| // Get current labels on the PR | |
| const { data: current } = await github.rest.issues.listLabelsOnIssue({ | |
| owner, | |
| repo, | |
| issue_number: pull_number, | |
| per_page: 100 | |
| }); | |
| const currentNames = new Set(current.map(l => l.name)); | |
| // Remove any existing pre-commit labels | |
| const preCommitRegex = /^pre-commit:/i; | |
| for (const name of currentNames) { | |
| if (preCommitRegex.test(name) && name !== newLabel) { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner, | |
| repo, | |
| issue_number: pull_number, | |
| name | |
| }); | |
| core.info(`Removed label ${name}`); | |
| } catch (err) { | |
| core.warning(`Failed to remove label ${name}: ${err.message}`); | |
| } | |
| } | |
| } | |
| // Ensure the new label exists (create if missing) | |
| async function ensureLabelExists(labelName) { | |
| try { | |
| await github.rest.issues.getLabel({ owner, repo, name: labelName }); | |
| } catch (e) { | |
| if (e.status === 404) { | |
| await github.rest.issues.createLabel({ | |
| owner, | |
| repo, | |
| name: labelName, | |
| color: labelColor, | |
| description: description, | |
| }); | |
| core.info(`Created label ${labelName}`); | |
| } else { | |
| throw e; | |
| } | |
| } | |
| } | |
| await ensureLabelExists(newLabel); | |
| // Add the label if it isn't already present | |
| if (!currentNames.has(newLabel)) { | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: pull_number, | |
| labels: [newLabel] | |
| }); | |
| core.info(`Applied label ${newLabel} to PR #${pull_number}`); | |
| } catch (error) { | |
| if (error.status === 403) { | |
| core.warning(`Permission denied: Cannot add label to PR. Repository may need to enable "Allow GitHub Actions to create and approve pull requests" in Settings > Actions > General.`); | |
| } else { | |
| throw error; | |
| } | |
| } | |
| } else { | |
| core.info(`Label ${newLabel} already present on PR #${pull_number}`); | |
| } | |
| core.info(`Pre-commit exit code: ${preCommitExitCode}`); | |
| - name: Fail the job if pre-commit failed | |
| if: steps.pre-commit.outputs.exit_code != '0' | |
| run: exit 1 | |
| - name: Print memory usage | |
| run: free -h | |
| test: | |
| name: Run Tests | |
| needs: setup | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| test_result: ${{ steps.run-tests.outputs.exit_code }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: 3.11.2 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y xvfb chromium-browser chromium-chromedriver | |
| - run: pip install poetry | |
| - run: poetry lock | |
| - run: poetry install --with dev | |
| - run: poetry run python manage.py collectstatic --noinput | |
| - name: Run tests | |
| id: run-tests | |
| shell: bash | |
| run: | | |
| set +e | |
| poetry run xvfb-run --auto-servernum python manage.py test -v 3 --failfast 2>&1 | tee test-output.txt | |
| echo "exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT | |
| exit 0 | |
| continue-on-error: true | |
| - name: Upload test output | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-output | |
| path: test-output.txt | |
| retention-days: 1 | |
| - name: Fail the job if tests failed | |
| if: steps.run-tests.outputs.exit_code != '0' | |
| run: exit 1 | |
| label-test-result: | |
| name: Label Test Result | |
| needs: test | |
| if: always() && github.event_name == 'pull_request_target' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| actions: read | |
| steps: | |
| - name: Download test output | |
| if: needs.test.outputs.test_result != '0' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: test-output | |
| path: . | |
| continue-on-error: true | |
| - name: Comment on PR if tests failed | |
| if: needs.test.outputs.test_result != '0' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const pull_number = context.issue.number; | |
| if (!pull_number) { | |
| core.warning('No pull request number found in context. Skipping comment.'); | |
| return; | |
| } | |
| let testOutput = 'Test output not available.'; | |
| try { | |
| testOutput = fs.readFileSync('test-output.txt', 'utf8'); | |
| } catch (e) { | |
| core.warning(`Could not read test output: ${e.message}`); | |
| } | |
| // Get last 100 lines of output | |
| const lines = testOutput.split('\n'); | |
| const last100Lines = lines.slice(-100).join('\n'); | |
| const truncated = lines.length > 100; | |
| const message = `## ❌ Tests failed | |
| The Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests. | |
| ### How to run tests locally | |
| \`\`\`bash | |
| # Install dependencies | |
| poetry install --with dev | |
| # Run all tests | |
| poetry run python manage.py test | |
| # Run tests with verbose output | |
| poetry run python manage.py test -v 3 | |
| # Run a specific test | |
| poetry run python manage.py test app.tests.TestClass.test_method | |
| \`\`\` | |
| <details> | |
| <summary>Test output${truncated ? ' (last 100 lines)' : ''}</summary> | |
| \`\`\`text | |
| ${last100Lines} | |
| \`\`\` | |
| </details> | |
| For more information, see the [Django testing documentation](https://docs.djangoproject.com/en/stable/topics/testing/).`; | |
| try { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pull_number, | |
| body: message | |
| }); | |
| core.info(`Added test failure comment to PR #${pull_number}`); | |
| } catch (error) { | |
| if (error.status === 403) { | |
| core.warning('Permission denied: Cannot comment on PR. Repository may need to enable "Allow GitHub Actions to create and approve pull requests" in Settings > Actions > General.'); | |
| } else { | |
| throw error; | |
| } | |
| } | |
| - name: Add test status label | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const pull_number = context.issue.number; | |
| // Validate PR context exists | |
| if (!pull_number) { | |
| core.warning('No pull request number found in context. Skipping label.'); | |
| return; | |
| } | |
| core.info(`Processing test result label for PR #${pull_number}`); | |
| // Determine label based on test exit code | |
| const testExitCode = '${{ needs.test.outputs.test_result }}'; | |
| core.info(`Test exit code from job output: ${testExitCode}`); | |
| const newLabel = testExitCode === '0' ? 'tests: passed' : 'tests: failed'; | |
| const labelColor = testExitCode === '0' ? '0e8a16' : 'e74c3c'; // Green or Red | |
| const description = testExitCode === '0' ? 'Django tests passed' : 'Django tests failed'; | |
| // Get current labels on the PR | |
| const { data: current } = await github.rest.issues.listLabelsOnIssue({ | |
| owner, | |
| repo, | |
| issue_number: pull_number, | |
| per_page: 100 | |
| }); | |
| const currentNames = new Set(current.map(l => l.name)); | |
| // Remove any existing test labels | |
| const testRegex = /^tests:/i; | |
| for (const name of currentNames) { | |
| if (testRegex.test(name) && name !== newLabel) { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner, | |
| repo, | |
| issue_number: pull_number, | |
| name | |
| }); | |
| core.info(`Removed label ${name}`); | |
| } catch (err) { | |
| core.warning(`Failed to remove label ${name}: ${err.message}`); | |
| } | |
| } | |
| } | |
| // Ensure the new label exists (create if missing) | |
| async function ensureLabelExists(labelName) { | |
| try { | |
| await github.rest.issues.getLabel({ owner, repo, name: labelName }); | |
| } catch (e) { | |
| if (e.status === 404) { | |
| await github.rest.issues.createLabel({ | |
| owner, | |
| repo, | |
| name: labelName, | |
| color: labelColor, | |
| description: description, | |
| }); | |
| core.info(`Created label ${labelName}`); | |
| } else { | |
| throw e; | |
| } | |
| } | |
| } | |
| await ensureLabelExists(newLabel); | |
| // Add the label if it isn't already present | |
| if (!currentNames.has(newLabel)) { | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: pull_number, | |
| labels: [newLabel] | |
| }); | |
| core.info(`Applied label ${newLabel} to PR #${pull_number}`); | |
| } catch (error) { | |
| if (error.status === 403) { | |
| core.warning(`Permission denied: Cannot add label to PR. Repository may need to enable "Allow GitHub Actions to create and approve pull requests" in Settings > Actions > General.`); | |
| } else { | |
| throw error; | |
| } | |
| } | |
| } else { | |
| core.info(`Label ${newLabel} already present on PR #${pull_number}`); | |
| } | |
| core.info(`Test exit code: ${testExitCode}`) | |
| docker-test: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker | |
| run: | | |
| docker --version | |
| # Use Docker Compose v2 (built-in Docker CLI plugin, no manual installation needed) | |
| docker compose version | |
| - name: Build Docker image | |
| run: | | |
| docker build -t my-app . | |
| - name: Run Docker container | |
| run: | | |
| docker run -d --name my-container my-app | |
| - run: docker exec my-container pip install poetry | |
| - run: docker exec my-container poetry lock | |
| - run: docker exec my-container poetry install --without dev --no-interaction | |
| - name: Clean up | |
| run: | | |
| docker stop my-container | |
| docker rm my-container | |
| - name: Print memory usage | |
| run: free -h |