mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-01 20:10:35 +01:00
# Description of Changes This pull request updates the CI/CD workflows and Gradle configuration to improve build reproducibility, security, and external dependency management. The main changes include standardizing Gradle setup across workflows, securely injecting Maven credentials, and enabling Gradle build caching. There are also minor improvements to dependency version management and plugin repository configuration. **CI/CD Workflow Improvements:** - Standardized Gradle setup across all GitHub Actions workflows by explicitly adding a `Setup Gradle` step using `gradle/actions/setup-gradle@v5.0.0` and specifying Gradle version 8.14. This replaces previous usages and ensures consistency. [[1]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R71-R81) [[2]](diffhunk://#diff-8d23782ae5caff72d55828bb25814854f5f2523f299d7dbcda4a3537dd84c5c3L157-R176) [[3]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R134-R144) [[4]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R206-R216) [[5]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R260-R264) [[6]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R331-R341) [[7]](diffhunk://#diff-3c0f521958c53ad27c967692b4d5480ead136acb33622ee97d39df814b1b202eR339-R351) [[8]](diffhunk://#diff-895b214ee023c8c26048a2a3b946cfb1ebc4f26fbc8a9c2fa54b77c12e763b6bL53-R54) [[9]](diffhunk://#diff-895b214ee023c8c26048a2a3b946cfb1ebc4f26fbc8a9c2fa54b77c12e763b6bL121-R127) [[10]](diffhunk://#diff-895b214ee023c8c26048a2a3b946cfb1ebc4f26fbc8a9c2fa54b77c12e763b6bR206-R217) [[11]](diffhunk://#diff-6a2e9fb077e57351f4a7e10d03b114e256298babdf06e7e7ae666781a5cf36a1R60-R70) [[12]](diffhunk://#diff-62dcbe64a950b4efb54d691e1e87451a8cd535400aa9ea1e40893de5b57cd73bL45-R46) [[13]](diffhunk://#diff-76056236de05155107f6a660f1e3956059e37338011b8f0e72188afcb9b17b6fL46-R56) [[14]](diffhunk://#diff-fd60dc2adec58c1005c4e4164e9c24362fd6082fd3ab0403e54d276d9835fa6eL42-R65) [[15]](diffhunk://#diff-b34ab107dd4bc92075b2e89b6f16e4a2813e267ca7c2afebdb1931a0a3900d5aR102-R114) [[16]](diffhunk://#diff-98b618771a57e1758961359ecacbac2cff7cfef29aa021c3bc294ae926c4ce5bL47-R51) - Enabled Gradle build cache (`--build-cache`) for all build-related commands in workflows, improving build performance and consistency. Also removed unnecessary `clean` commands before builds to further optimize workflow times. [[1]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R71-R81) [[2]](diffhunk://#diff-8d23782ae5caff72d55828bb25814854f5f2523f299d7dbcda4a3537dd84c5c3L157-R176) [[3]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R134-R144) [[4]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R206-R216) [[5]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R331-R341) [[6]](diffhunk://#diff-3c0f521958c53ad27c967692b4d5480ead136acb33622ee97d39df814b1b202eR339-R351) [[7]](diffhunk://#diff-895b214ee023c8c26048a2a3b946cfb1ebc4f26fbc8a9c2fa54b77c12e763b6bL134-R144) [[8]](diffhunk://#diff-895b214ee023c8c26048a2a3b946cfb1ebc4f26fbc8a9c2fa54b77c12e763b6bR206-R217) [[9]](diffhunk://#diff-6a2e9fb077e57351f4a7e10d03b114e256298babdf06e7e7ae666781a5cf36a1R60-R70) [[10]](diffhunk://#diff-76056236de05155107f6a660f1e3956059e37338011b8f0e72188afcb9b17b6fL46-R56) [[11]](diffhunk://#diff-fd60dc2adec58c1005c4e4164e9c24362fd6082fd3ab0403e54d276d9835fa6eL42-R65) [[12]](diffhunk://#diff-b34ab107dd4bc92075b2e89b6f16e4a2813e267ca7c2afebdb1931a0a3900d5aR102-R114) [[13]](diffhunk://#diff-98b618771a57e1758961359ecacbac2cff7cfef29aa021c3bc294ae926c4ce5bL47-R51) **Security and Dependency Management:** - Injected Maven credentials (`MAVEN_USER`, `MAVEN_PASSWORD`, `MAVEN_PUBLIC_URL`) as environment variables in all relevant workflow steps, supporting secure access to private or custom Maven repositories. [[1]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R71-R81) [[2]](diffhunk://#diff-8d23782ae5caff72d55828bb25814854f5f2523f299d7dbcda4a3537dd84c5c3L157-R176) [[3]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R134-R144) [[4]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R206-R216) [[5]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R290-R293) [[6]](diffhunk://#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721R331-R341) [[7]](diffhunk://#diff-3c0f521958c53ad27c967692b4d5480ead136acb33622ee97d39df814b1b202eR339-R351) [[8]](diffhunk://#diff-895b214ee023c8c26048a2a3b946cfb1ebc4f26fbc8a9c2fa54b77c12e763b6bR66-R69) [[9]](diffhunk://#diff-895b214ee023c8c26048a2a3b946cfb1ebc4f26fbc8a9c2fa54b77c12e763b6bL134-R144) [[10]](diffhunk://#diff-895b214ee023c8c26048a2a3b946cfb1ebc4f26fbc8a9c2fa54b77c12e763b6bR281-R283) [[11]](diffhunk://#diff-62dcbe64a950b4efb54d691e1e87451a8cd535400aa9ea1e40893de5b57cd73bR57-R60) [[12]](diffhunk://#diff-76056236de05155107f6a660f1e3956059e37338011b8f0e72188afcb9b17b6fR73-R76) [[13]](diffhunk://#diff-fd60dc2adec58c1005c4e4164e9c24362fd6082fd3ab0403e54d276d9835fa6eL42-R65) [[14]](diffhunk://#diff-b34ab107dd4bc92075b2e89b6f16e4a2813e267ca7c2afebdb1931a0a3900d5aR178-R180) [[15]](diffhunk://#diff-98b618771a57e1758961359ecacbac2cff7cfef29aa021c3bc294ae926c4ce5bL47-R51) - Added a `pluginManagement` block in `settings.gradle` to allow Gradle plugins to be resolved from a custom Maven repository if specified by environment variables, increasing flexibility for plugin sourcing. **Build and Dependency Versioning:** - Updated `app/proprietary/build.gradle` to use the `bouncycastleVersion` variable for the Bouncy Castle dependency version, improving maintainability and consistency of dependency versioning. **Workflow Trigger Improvements:** - Expanded the file path triggers in `.github/workflows/sync_files_v2.yml` to include additional Gradle build files, ensuring the workflow runs when any core build files are changed. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
513 lines
20 KiB
YAML
513 lines
20 KiB
YAML
name: PR Deployment via Comment
|
|
|
|
on:
|
|
issue_comment:
|
|
types: [created]
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
|
|
jobs:
|
|
check-comment:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
issues: write
|
|
if: |
|
|
vars.CI_PROFILE != 'lite' &&
|
|
github.event.issue.pull_request &&
|
|
(
|
|
contains(github.event.comment.body, 'prdeploy') ||
|
|
contains(github.event.comment.body, 'deploypr')
|
|
)
|
|
&&
|
|
(
|
|
github.event.comment.user.login == 'frooodle' ||
|
|
github.event.comment.user.login == 'sf298' ||
|
|
github.event.comment.user.login == 'Ludy87' ||
|
|
github.event.comment.user.login == 'balazs-szucs' ||
|
|
github.event.comment.user.login == 'reecebrowne' ||
|
|
github.event.comment.user.login == 'DarioGii' ||
|
|
github.event.comment.user.login == 'EthanHealy01' ||
|
|
github.event.comment.user.login == 'jbrunton96' ||
|
|
github.event.comment.user.login == 'ConnorYoh'
|
|
)
|
|
outputs:
|
|
pr_number: ${{ steps.get-pr.outputs.pr_number }}
|
|
comment_id: ${{ github.event.comment.id }}
|
|
disable_security: ${{ steps.check-security-flag.outputs.disable_security }}
|
|
enable_pro: ${{ steps.check-pro-flag.outputs.enable_pro }}
|
|
enable_enterprise: ${{ steps.check-pro-flag.outputs.enable_enterprise }}
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Checkout PR
|
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
|
|
|
- name: Setup GitHub App Bot
|
|
if: github.actor != 'dependabot[bot]'
|
|
id: setup-bot
|
|
uses: ./.github/actions/setup-bot
|
|
continue-on-error: true
|
|
with:
|
|
app-id: ${{ secrets.GH_APP_ID }}
|
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
|
|
|
- name: Get PR data
|
|
id: get-pr
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
with:
|
|
script: |
|
|
const prNumber = context.payload.issue.number;
|
|
console.log(`PR Number: ${prNumber}`);
|
|
core.setOutput('pr_number', prNumber);
|
|
|
|
- name: Check for security/login flag
|
|
id: check-security-flag
|
|
env:
|
|
COMMENT_BODY: ${{ github.event.comment.body }}
|
|
run: |
|
|
if [[ "$COMMENT_BODY" == *"security"* ]] || [[ "$COMMENT_BODY" == *"login"* ]]; then
|
|
echo "Security flags detected in comment"
|
|
echo "disable_security=false" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "No security flags detected in comment"
|
|
echo "disable_security=true" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Check for pro flag
|
|
id: check-pro-flag
|
|
env:
|
|
COMMENT_BODY: ${{ github.event.comment.body }}
|
|
run: |
|
|
if [[ "$COMMENT_BODY" == *"pro"* ]] || [[ "$COMMENT_BODY" == *"premium"* ]]; then
|
|
echo "pro flags detected in comment"
|
|
echo "enable_pro=true" >> $GITHUB_OUTPUT
|
|
echo "enable_enterprise=false" >> $GITHUB_OUTPUT
|
|
elif [[ "$COMMENT_BODY" == *"enterprise"* ]]; then
|
|
echo "enterprise flags detected in comment"
|
|
echo "enable_enterprise=true" >> $GITHUB_OUTPUT
|
|
echo "enable_pro=true" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "No pro or enterprise flags detected in comment"
|
|
echo "enable_pro=false" >> $GITHUB_OUTPUT
|
|
echo "enable_enterprise=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Add 'in_progress' reaction to comment
|
|
id: add-eyes-reaction
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
with:
|
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
|
script: |
|
|
console.log(`Adding eyes reaction to comment ID: ${context.payload.comment.id}`);
|
|
try {
|
|
const { data: reaction } = await github.rest.reactions.createForIssueComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: context.payload.comment.id,
|
|
content: 'eyes'
|
|
});
|
|
console.log(`Added reaction with ID: ${reaction.id}`);
|
|
return { success: true, id: reaction.id };
|
|
} catch (error) {
|
|
console.error(`Failed to add reaction: ${error.message}`);
|
|
console.error(error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
deploy-pr:
|
|
needs: check-comment
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
issues: write
|
|
pull-requests: write
|
|
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Checkout PR
|
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
|
|
|
- name: Setup GitHub App Bot
|
|
if: github.actor != 'dependabot[bot]'
|
|
id: setup-bot
|
|
uses: ./.github/actions/setup-bot
|
|
continue-on-error: true
|
|
with:
|
|
app-id: ${{ secrets.GH_APP_ID }}
|
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
|
|
|
- name: Checkout PR
|
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
|
with:
|
|
ref: refs/pull/${{ needs.check-comment.outputs.pr_number }}/merge
|
|
token: ${{ steps.setup-bot.outputs.token }}
|
|
|
|
- name: Set up JDK 21
|
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
|
with:
|
|
java-version: "21"
|
|
distribution: "temurin"
|
|
|
|
- name: Setup Gradle
|
|
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
|
with:
|
|
gradle-version: 8.14
|
|
|
|
- name: Run Gradle Command
|
|
run: |
|
|
if [ "${{ needs.check-comment.outputs.disable_security }}" == "true" ]; then
|
|
export DISABLE_ADDITIONAL_FEATURES=true
|
|
else
|
|
export DISABLE_ADDITIONAL_FEATURES=false
|
|
fi
|
|
./gradlew build
|
|
env:
|
|
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
|
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
|
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
|
|
STIRLING_PDF_DESKTOP_UI: false
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
|
|
|
- name: Login to Docker Hub
|
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
with:
|
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
password: ${{ secrets.DOCKER_HUB_API }}
|
|
|
|
- name: Build and push PR-specific image
|
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
|
with:
|
|
context: .
|
|
file: ./docker/embedded/Dockerfile
|
|
push: true
|
|
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
|
|
build-args: VERSION_TAG=alpha
|
|
platforms: linux/amd64
|
|
|
|
- name: Set up SSH
|
|
run: |
|
|
mkdir -p ~/.ssh/
|
|
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
|
|
sudo chmod 600 ../private.key
|
|
|
|
- name: Deploy to VPS
|
|
id: deploy
|
|
run: |
|
|
# Set security settings based on flags
|
|
if [ "${{ needs.check-comment.outputs.disable_security }}" == "false" ]; then
|
|
DISABLE_ADDITIONAL_FEATURES="false"
|
|
LOGIN_SECURITY="true"
|
|
SECURITY_STATUS="🔒 Security Enabled"
|
|
else
|
|
DISABLE_ADDITIONAL_FEATURES="true"
|
|
LOGIN_SECURITY="false"
|
|
SECURITY_STATUS="Security Disabled"
|
|
fi
|
|
|
|
# Set pro/enterprise settings (enterprise implies pro)
|
|
if [ "${{ needs.check-comment.outputs.enable_enterprise }}" == "true" ]; then
|
|
PREMIUM_ENABLED="true"
|
|
PREMIUM_KEY="${{ secrets.ENTERPRISE_KEY }}"
|
|
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
|
|
elif [ "${{ needs.check-comment.outputs.enable_pro }}" == "true" ]; then
|
|
PREMIUM_ENABLED="true"
|
|
PREMIUM_KEY="${{ secrets.PREMIUM_KEY }}"
|
|
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
|
|
else
|
|
PREMIUM_ENABLED="false"
|
|
PREMIUM_KEY=""
|
|
PREMIUM_PROFEATURES_AUDIT_ENABLED="false"
|
|
fi
|
|
|
|
# First create the docker-compose content locally
|
|
cat > docker-compose.yml << EOF
|
|
version: '3.3'
|
|
services:
|
|
stirling-pdf:
|
|
container_name: stirling-pdf-pr-${{ needs.check-comment.outputs.pr_number }}
|
|
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
|
|
ports:
|
|
- "${{ needs.check-comment.outputs.pr_number }}:8080"
|
|
volumes:
|
|
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/data:/usr/share/tessdata:rw
|
|
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/config:/configs:rw
|
|
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/logs:/logs:rw
|
|
environment:
|
|
DISABLE_ADDITIONAL_FEATURES: "${DISABLE_ADDITIONAL_FEATURES}"
|
|
SECURITY_ENABLELOGIN: "${LOGIN_SECURITY}"
|
|
SYSTEM_DEFAULTLOCALE: en-GB
|
|
UI_APPNAME: "Stirling-PDF PR#${{ needs.check-comment.outputs.pr_number }}"
|
|
UI_HOMEDESCRIPTION: "PR#${{ needs.check-comment.outputs.pr_number }} for Stirling-PDF Latest"
|
|
UI_APPNAMENAVBAR: "PR#${{ needs.check-comment.outputs.pr_number }}"
|
|
SYSTEM_MAXFILESIZE: "100"
|
|
METRICS_ENABLED: "true"
|
|
SYSTEM_GOOGLEVISIBILITY: "false"
|
|
PREMIUM_KEY: "${PREMIUM_KEY}"
|
|
PREMIUM_ENABLED: "${PREMIUM_ENABLED}"
|
|
PREMIUM_PROFEATURES_AUDIT_ENABLED: "${PREMIUM_PROFEATURES_AUDIT_ENABLED}"
|
|
restart: on-failure:5
|
|
EOF
|
|
|
|
# Then copy the file and execute commands
|
|
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/docker-compose.yml
|
|
|
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << ENDSSH
|
|
# Create PR-specific directories
|
|
mkdir -p /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/{data,config,logs}
|
|
|
|
# Move docker-compose file to correct location
|
|
mv /tmp/docker-compose.yml /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/docker-compose.yml
|
|
|
|
# Start or restart the container
|
|
cd /stirling/PR-${{ needs.check-comment.outputs.pr_number }}
|
|
docker-compose pull
|
|
docker-compose up -d
|
|
ENDSSH
|
|
|
|
# Set output for use in PR comment
|
|
echo "security_status=${SECURITY_STATUS}" >> $GITHUB_ENV
|
|
|
|
- name: Add success reaction to comment
|
|
if: success()
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
with:
|
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
|
script: |
|
|
console.log(`Adding rocket reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
|
|
try {
|
|
const { data: reaction } = await github.rest.reactions.createForIssueComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: ${{ needs.check-comment.outputs.comment_id }},
|
|
content: 'rocket'
|
|
});
|
|
console.log(`Added rocket reaction with ID: ${reaction.id}`);
|
|
} catch (error) {
|
|
console.error(`Failed to add reaction: ${error.message}`);
|
|
console.error(error);
|
|
}
|
|
|
|
// add label to PR
|
|
const prNumber = ${{ needs.check-comment.outputs.pr_number }};
|
|
try {
|
|
await github.rest.issues.addLabels({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber,
|
|
labels: ['pr-deployed']
|
|
});
|
|
console.log(`Added 'pr-deployed' label to PR #${prNumber}`);
|
|
} catch (error) {
|
|
console.error(`Failed to add label to PR: ${error.message}`);
|
|
console.error(error);
|
|
}
|
|
|
|
- name: Add failure reaction to comment
|
|
if: failure()
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
with:
|
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
|
script: |
|
|
console.log(`Adding -1 reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
|
|
try {
|
|
const { data: reaction } = await github.rest.reactions.createForIssueComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: ${{ needs.check-comment.outputs.comment_id }},
|
|
content: '-1'
|
|
});
|
|
console.log(`Added -1 reaction with ID: ${reaction.id}`);
|
|
} catch (error) {
|
|
console.error(`Failed to add reaction: ${error.message}`);
|
|
console.error(error);
|
|
}
|
|
|
|
- name: Post deployment URL to PR
|
|
if: success()
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
with:
|
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
|
script: |
|
|
const { GITHUB_REPOSITORY } = process.env;
|
|
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
|
const prNumber = ${{ needs.check-comment.outputs.pr_number }};
|
|
const securityStatus = process.env.security_status || "Security Disabled";
|
|
|
|
const deploymentUrl = `http://${{ secrets.NEW_VPS_HOST }}:${prNumber}`;
|
|
const commentBody = `## 🚀 PR Test Deployment\n\n` +
|
|
`Your PR has been deployed for testing!\n\n` +
|
|
`🔗 **Test URL:** [${deploymentUrl}](${deploymentUrl})\n` +
|
|
`${securityStatus}\n\n` +
|
|
`This deployment will be automatically cleaned up when the PR is closed.\n\n`;
|
|
|
|
await github.rest.issues.createComment({
|
|
owner: repoOwner,
|
|
repo: repoName,
|
|
issue_number: prNumber,
|
|
body: commentBody
|
|
});
|
|
|
|
- name: Cleanup temporary files
|
|
if: always()
|
|
run: |
|
|
echo "Cleaning up temporary files..."
|
|
rm -f ../private.key docker-compose.yml
|
|
echo "Cleanup complete."
|
|
continue-on-error: true
|
|
|
|
handle-label-commands:
|
|
if: ${{ github.event.issue.pull_request != null }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Check out the repository
|
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
|
|
|
- name: Setup GitHub App Bot
|
|
id: setup-bot
|
|
uses: ./.github/actions/setup-bot
|
|
with:
|
|
app-id: ${{ secrets.GH_APP_ID }}
|
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
|
|
|
- name: Apply label commands
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
with:
|
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
|
script: |
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const { comment, issue } = context.payload;
|
|
const commentBody = comment?.body ?? '';
|
|
if (!commentBody.includes('::label::')) {
|
|
core.info('No label commands detected in comment.');
|
|
return;
|
|
}
|
|
|
|
const configPath = path.join(process.env.GITHUB_WORKSPACE, '.github', 'config', 'repo_devs.json');
|
|
const repoDevsConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
const label_changer = (repoDevsConfig.label_changer || []).map((login) => login.toLowerCase());
|
|
|
|
const commenter = (comment?.user?.login || '').toLowerCase();
|
|
if (!label_changer.includes(commenter)) {
|
|
core.info(`User ${commenter} is not authorized to manage labels.`);
|
|
return;
|
|
}
|
|
|
|
const labelsConfigPath = path.join(process.env.GITHUB_WORKSPACE, '.github', 'labels.yml');
|
|
const labelsFile = fs.readFileSync(labelsConfigPath, 'utf8');
|
|
|
|
const labelNameMap = new Map();
|
|
for (const match of labelsFile.matchAll(/-\s+name:\s*(?:"([^"]+)"|'([^']+)'|([^\n]+))/g)) {
|
|
const labelName = (match[1] ?? match[2] ?? match[3] ?? '').trim();
|
|
|
|
if (!labelName) {
|
|
continue;
|
|
}
|
|
const normalized = labelName.toLowerCase();
|
|
if (!labelNameMap.has(normalized)) {
|
|
labelNameMap.set(normalized, labelName);
|
|
}
|
|
}
|
|
|
|
if (!labelNameMap.size) {
|
|
core.warning('No labels could be read from .github/labels.yml; aborting label commands.');
|
|
return;
|
|
}
|
|
|
|
let allowedLabelNames = new Set(labelNameMap.values());
|
|
|
|
const labelsToAdd = new Set();
|
|
const labelsToRemove = new Set();
|
|
const commandRegex = /^(\w+)::(label)::"([^"]+)"/gim;
|
|
let match;
|
|
while ((match = commandRegex.exec(commentBody)) !== null) {
|
|
core.info(`Found label command: ${match[0]} (action: ${match[1]}, label: ${match[2]}, labelName: ${match[3]})`);
|
|
const action = match[1].toLowerCase();
|
|
const labelName = match[3].trim();
|
|
|
|
if (!labelName) {
|
|
continue;
|
|
}
|
|
|
|
const normalized = labelName.toLowerCase();
|
|
const resolvedLabelName = labelNameMap.get(normalized);
|
|
if (action === 'add') {
|
|
if (!resolvedLabelName) {
|
|
core.warning(`Label "${labelName}" is not defined in .github/labels.yml and cannot be added.`);
|
|
continue;
|
|
}
|
|
if (!allowedLabelNames.has(resolvedLabelName)) {
|
|
core.warning(`Label "${resolvedLabelName}" is not allowed for add commands and will be skipped.`);
|
|
continue;
|
|
}
|
|
labelsToAdd.add(resolvedLabelName);
|
|
} else if (action === 'rm') {
|
|
const labelToRemove = resolvedLabelName ?? labelName;
|
|
if (!resolvedLabelName) {
|
|
core.warning(`Label "${labelName}" is not defined in .github/labels.yml; attempting to remove as provided.`);
|
|
}
|
|
labelsToRemove.add(labelToRemove);
|
|
}
|
|
}
|
|
|
|
const addLabels = Array.from(labelsToAdd);
|
|
const removeLabels = Array.from(labelsToRemove);
|
|
|
|
if (!addLabels.length && !removeLabels.length) {
|
|
core.info('No valid label commands found after parsing.');
|
|
return;
|
|
}
|
|
|
|
const issueParams = {
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
};
|
|
|
|
if (addLabels.length) {
|
|
core.info(`Adding labels: ${addLabels.join(', ')}`);
|
|
await github.rest.issues.addLabels({
|
|
...issueParams,
|
|
labels: addLabels,
|
|
});
|
|
}
|
|
|
|
for (const labelName of removeLabels) {
|
|
core.info(`Removing label: ${labelName}`);
|
|
try {
|
|
await github.rest.issues.removeLabel({
|
|
...issueParams,
|
|
name: labelName,
|
|
});
|
|
} catch (error) {
|
|
if (error.status === 404) {
|
|
core.warning(`Label "${labelName}" was not present on the pull request.`);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
await github.rest.issues.deleteComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: comment.id,
|
|
});
|
|
core.info('Processed label commands and deleted the comment.');
|