Skip to content

nolanpro send deploy EKS 🚀 #333

nolanpro send deploy EKS 🚀

nolanpro send deploy EKS 🚀 #333

Workflow file for this run

name: deploy-k8s
run-name: ${{ github.actor }} send deploy EKS 🚀
on:
pull_request:
types: [opened, reopened, synchronize, edited, closed]
#schedule:
# - cron: '30 2 * * *' # run daily
workflow_dispatch:
inputs:
delete:
description: 'CI Instance ID to delete. If present, all other jobs will be skipped.'
required: false
default: ''
workflow_call:
env:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
AWS_URL: ${{ secrets.AWS_URL }}
pull_req_id: ${{github.event.pull_request.number}}
DATE: $(date -d '-1 day' '+%Y-%m-%d'|sed 's/-//g')
CURRENT_DATE: $(date '+%Y-%m-%d %H:%M:%S'|sed 's/-//g')
CI_PACKAGE_BRANCH: ${{ github.event.pull_request.head.ref || github.event.ref || 'develop' }}
CI_PROJECT: ${{github.event.pull_request.head.repo.name || github.event.repository.name || 'processmaker' }}
CI_PR_BODY: ${{ github.event_name == 'schedule' && 'No ci tags needed here' || github.event.pull_request.body }}
IMAGE_TAG: $(echo "$CI_PROJECT-$CI_PACKAGE_BRANCH" | sed "s;/;-;g" | sed "s/refs-heads-//g")
DEPLOY: ${{ secrets.DEPLOY }}
GH_USER: ${{ secrets.GH_USER }}
GH_EMAIL: ${{ secrets.GH_EMAIL }}
DOM_EKS: ${{ secrets.DOM_EKS }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
BUILD_BASE: ${{ (contains(github.event.pull_request.body, 'ci:build-base') || github.event_name == 'schedule') && '1' || '0' }}
MULTITENANCY: ${{ (contains(github.event.pull_request.body, 'ci:multitenancy')) && 'true' || 'false' }}
BASE_IMAGE: ${{ secrets.REGISTRY_HOST }}/processmaker/processmaker:base
CUSTOMER_LICENSES_PAT: ${{ secrets.CUSTOMER_LICENSES_PAT }}
SKIP_REDEPLOY: ${{ (contains(github.event.pull_request.body, 'ci:skip-redeploy')) && 'true' || 'false' }}
CI_REDEPLOY: ${{ (contains(github.event.pull_request.body, 'ci:redeploy')) && 'true' || 'false' }}
# K8S_BRANCH: ${{ contains(github.event.pull_request.body, 'ci:next') && 'next' || 'release-2024-fall' }}
PMAI_HOST: ${{ contains(github.event.pull_request.body, 'ci:pmai_dev') && 'pmai-svc.pmai-system-dev.svc.cluster.local' || 'pmai-svc.pmai-system.svc.cluster.local' }}
PMAI_HOST_WS: ${{ contains(github.event.pull_request.body, 'ci:pmai_dev') && 'pmai-dev-ws.engk8s.processmaker.net' || 'pmai-ws.engk8s.processmaker.net' }}
concurrency:
# workflow_dispatch deletes share github.ref (refs/heads/main) when no PR is
# present, so include the delete tag to avoid canceling parallel delete runs.
group: ${{ github.workflow }}-${{ github.event.pull_request.number || (inputs.delete != '' && format('delete-{0}', inputs.delete)) || github.ref }}
cancel-in-progress: true
jobs:
imageEKS:
name: build-docker-image-EKS
needs: [deleteEKS]
if: |
!cancelled() &&
(needs.deleteEKS.result == 'success' || needs.deleteEKS.result == 'skipped') &&
github.event.action != 'closed' &&
inputs.delete == ''
runs-on: ${{ vars.RUNNER }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: processmaker/.github
- name: Common
uses: ./.github/actions/common
with:
token: ${{ secrets.GIT_TOKEN }}
- name: Set image name
run: |
echo "IMAGE=${{ secrets.REGISTRY_HOST }}/processmaker/enterprise:$RESOLVED_IMAGE_TAG" >> $GITHUB_ENV
- name: Generate image EKS
if: ${{ !contains(github.event.pull_request.body, 'ci:skip-build') }}
run: |
cd pm4-k8s-distribution/images
export CI_RELEASE_BRANCH=$RELEASE_BRANCH
branch=$(echo "${{ env.CI_PACKAGE_BRANCH }}" | sed 's/refs-heads-//g') tag=${{env.IMAGE_TAG}} bash build.k8s-cicd.sh
echo "VERSION=${{ env.IMAGE_TAG }}" >> $GITHUB_ENV
- name: List Images
run: |
docker images
# - name: Run Trivy vulnerability scanner
# uses: aquasecurity/trivy-action@master
# with:
# image-ref: processmaker/enterprise:${{ env.VERSION }}
# format: 'table'
# exit-code: '0'
# ignore-unfixed: false
# vuln-type: 'os,library'
# scanners: 'vuln,secret'
# severity: 'MEDIUM,HIGH,CRITICAL'
# env:
# TRIVY_TIMEOUT: 30m
- name: Login to Harbor
uses: docker/login-action@v2
with:
registry: ${{ secrets.REGISTRY_HOST }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Push Enterprise Image to Harbor
if: ${{ !contains(github.event.pull_request.body, 'ci:skip-build') }}
run: |
docker tag processmaker/enterprise:${{env.IMAGE_TAG}} ${{ secrets.REGISTRY_HOST }}/processmaker/enterprise:${{env.IMAGE_TAG}}
docker push ${{ secrets.REGISTRY_HOST }}/processmaker/enterprise:${{env.IMAGE_TAG}}
- name: Check rate limit on failure
if: failure()
run: |
echo "=== Checking GitHub API rate limit status ==="
curl -I --header "Authorization: Bearer ${{ secrets.GIT_TOKEN }}" https://api.github.com
echo ""
echo "=== Rate limit check complete ==="
deployEKS:
name: deploy-EKS
# !cancelled() opts out of the skip that cascades down from deleteEKS when it does not
# run, without also opting out of concurrency cancellation the way always() does.
if: |
!cancelled() &&
needs.imageEKS.result == 'success' &&
(contains(github.event.pull_request.body, 'ci:deploy') ||
contains(github.event.pull_request.body, 'ci:redeploy'))
needs: imageEKS
runs-on: ${{ vars.RUNNER }}
steps:
- name: Checkout .github repo
uses: actions/checkout@v4
with:
repository: processmaker/.github
ref: main
- name: Common
uses: ./.github/actions/common
with:
token: ${{ secrets.GIT_TOKEN }}
- name: Install pm4-tools
run: |
cd pm4-k8s-distribution/images/pm4-tools
composer install --no-interaction
cd ..
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Set up kubectl
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
echo ${{ secrets.AWS_ACCESS_KEY_ID }} | md5sum
- name: Authenticate with Amazon EKS
run: aws eks update-kubeconfig --region us-east-1 --name pm4-eng
- name: Deploy instance EKS
env:
IMAGE_TAG: ${{ env.IMAGE_TAG }}
CURRENT_DATE: ${{ env.CURRENT_DATE }}
HELM_REPO: ${{ secrets.HELM_REPO }}
HELM_USERNAME: ${{ secrets.HELM_USERNAME }}
HELM_PASSWORD: ${{ secrets.HELM_PASSWORD }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPEN_AI_SECRET: ${{ secrets.OPENAI_API_KEY }}
ANALYTICS_AWS_ACCESS_KEY: ${{ secrets.ANALYTICS_AWS_ACCESS_KEY }}
ANALYTICS_AWS_SECRET_KEY: ${{ secrets.ANALYTICS_AWS_SECRET_KEY }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST }}
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
TWILIO_SID: ${{ secrets.TWILIO_SID }}
TWILIO_TOKEN: ${{ secrets.TWILIO_TOKEN }}
versionHelm: ${{ env.versionHelm }}
DOM_EKS: ${{ env.DOM_EKS }}
KEYCLOAK_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET }}
KEYCLOAK_PASSWORD: ${{ secrets.KEYCLOAK_PASSWORD }}
CUSTOMER_LICENSES_PAT: ${{ secrets.CUSTOMER_LICENSES_PAT }}
RDS_ADMIN_USERNAME: ${{ secrets.RDS_ADMIN_USERNAME }}
RDS_ADMIN_PASSWORD: ${{ secrets.RDS_ADMIN_PASSWORD }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
instance=$(echo -n ${{env.IMAGE_TAG}} | md5sum | head -c 10)
echo "INSTANCE: $instance"
echo "IMAGE_TAG: $IMAGE_TAG"
if [[ "${{ env.SKIP_REDEPLOY }}" != "true" ]]; then
sed -i "s#{{INSTANCE}}#$instance#g" .github/scripts/deploy-instance.sh
sed -i "s#{{INSTANCE}}#$instance#g" .github/templates/instance.yaml
sed -i "s#{{INSTANCE}}#$instance#g" .github/templates/db.yaml
sed -i "s#{{IMAGE_TAG}}#$IMAGE_TAG#g" .github/templates/instance.yaml
sed -i "s#{{KEYCLOAK_CLIENT_SECRET}}#$KEYCLOAK_CLIENT_SECRET#g" .github/templates/instance.yaml
sed -i "s#{{KEYCLOAK_PASSWORD}}#$KEYCLOAK_PASSWORD#g" .github/templates/instance.yaml
sed -i "s#{{CUSTOMER_LICENSES_PAT}}#$CUSTOMER_LICENSES_PAT#g" .github/templates/instance.yaml
sed -i "s#{{MYSQL_USER}}#$RDS_ADMIN_USERNAME#g" .github/templates/instance.yaml
sed -i "s#{{MYSQL_PASSWORD}}#$RDS_ADMIN_PASSWORD#g" .github/templates/instance.yaml
sed -i "s#{{MULTITENANCY}}#$MULTITENANCY#g" .github/templates/instance.yaml
sed -i "s#{{PMAI_HOST}}#$PMAI_HOST#g" .github/templates/instance.yaml
sed -i "s#{{PMAI_HOST_WS}}#$PMAI_HOST_WS#g" .github/templates/instance.yaml
sed -i "s#{{MYSQL_USERNAME}}#$RDS_ADMIN_USERNAME#g" .github/templates/db.yaml
sed -i "s#{{MYSQL_PASSWORD}}#$RDS_ADMIN_PASSWORD#g" .github/templates/db.yaml
echo "=== Checking instance.yaml after replacements ==="
cat .github/templates/instance.yaml
echo "=== Checking db.yaml after replacements ==="
cat .github/templates/db.yaml
chmod +x .github/scripts/deploy-instance.sh
bash .github/scripts/deploy-instance.sh
fi
if [ "$MULTITENANCY" = "true" ]; then
export INSTANCE_URL="https://tenant-1.ci-$instance.engk8s.processmaker.net"
else
export INSTANCE_URL="https://ci-$instance.engk8s.processmaker.net"
fi
echo "Instance URL: $INSTANCE_URL"
bash .github/scripts/gh_comment.sh "$CI_PROJECT" "$pull_req_id"
runAPITest:
name: Run API Tests
needs: [deployEKS]
if: |
!cancelled() &&
needs.deployEKS.result == 'success' &&
contains(github.event.pull_request.body, 'ci:api-test')
runs-on: ${{ vars.RUNNER }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: processmaker/.github
- name: Common
uses: ./.github/actions/common
with:
token: ${{ secrets.GIT_TOKEN }}
- name: Install pm4-tools
run: |
echo "versionHelm=$(grep "version:" "pm4-k8s-distribution/charts/enterprise/Chart.yaml" | awk '{print $2}' | sed 's/\"//g')" >> $GITHUB_ENV
cd pm4-k8s-distribution/images/pm4-tools
composer install --no-interaction
cd ..
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID1 }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY1 }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Set up kubectl
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
- name: Authenticate with Amazon EKS
run: aws eks update-kubeconfig --region us-east-1 --name pm4-eng
- name: Run the API tests
run: |
INSTANCE=$(echo -n ${{env.IMAGE_TAG}} | md5sum | head -c 10)
namespace="ci-$INSTANCE-ns-pm4"
pr_body=$(jq -r .pull_request.body < "$GITHUB_EVENT_PATH" | base64)
kubectl get pods --namespace=$namespace
pod_names=$(kubectl get pods --namespace=$namespace --field-selector=status.phase=Running -o jsonpath="{.items[*].metadata.name}" | tr ' ' '\n' | grep -E '(-processmaker-scheduler-)')
for pod in $pod_names; do
code='
has_processmaker=$(ls /opt | grep processmaker)
has_sudo=$(ls /usr/bin | grep sudo)
has_php=$(ls /usr/bin | grep php)
if [ ! -z "$has_processmaker" ] && [ ! -z "$has_sudo" ] && [ ! -z "$has_php" ]; then
echo $pr_body | base64 -d > /tmp/pr_body
cd /opt/processmaker
docker system prune -af
sudo -u nginx php artisan package-api-testing:run --body="$pr_body"
else
exit 1
fi'
kubectl exec -n $namespace $pod -- /bin/sh -c "pr_body='${pr_body}';${code}" | tee /tmp/comment.md && break || true
done
# Send the content of /tmp/comment.md as a PR comment
MESSAGE=$(cat /tmp/comment.md)
GIT_TOKEN=${{ secrets.GIT_TOKEN }}
GITHUB_REPOSITORY=${{ github.repository }}
PR_NUMBER=$(jq -r .number < "$GITHUB_EVENT_PATH")
if [ -z "$PR_NUMBER" ]; then
echo "The PR number is not available. Make sure this script is executed in a context of Pull Request."
exit 1
fi
URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments"
json_payload=$(jq -n --arg message "$MESSAGE" '{"body": $message}')
curl -s \
-H "Authorization: token ${GIT_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
-d "$json_payload" \
"${URL}"
runTestbench:
name: Run Testbench
needs: [deployEKS]
if: |
!cancelled() &&
needs.deployEKS.result == 'success' &&
contains(github.event.pull_request.body, 'ci:run-testbench')
runs-on: ${{ vars.RUNNER }}
steps:
- name: Checkout testbench
uses: actions/checkout@v4
with:
repository: ProcessMaker/testbench
path: testbench
- name: Run testbench
working-directory: testbench
env:
SITE_NAME: CI
INSTANCE_PASSWORD: ${{ secrets.CI_INSTANCE_PASSWORD }}
CONTEXT_PATH: ./context
TCP_TUNNELS: "mailserver:587 mailserver:993"
run: |
instance=$(echo -n ${{env.IMAGE_TAG}} | md5sum | head -c 10)
echo "INSTANCE: $instance"
if [ "${{ env.MULTITENANCY }}" = "true" ]; then
export URL="https://tenant-1.ci-$instance.engk8s.processmaker.net"
else
export URL="https://ci-$instance.engk8s.processmaker.net"
fi
echo "URL: $URL"
export USER_GROUP="$(id -u):$(id -g)"
chmod +x ./start.sh
./start.sh
deleteEKS:
name: Delete Instance
if: github.event.action == 'closed' || inputs.delete != '' || contains(github.event.pull_request.body, 'ci:redeploy')
runs-on: self-hosted
# GITHUB_TOKEN needs write to strip ci:redeploy from the PR body.
# Using github.token (not GIT_TOKEN) so the edit does not trigger a new workflow run.
permissions:
pull-requests: write
contents: read
steps:
- name: Delete instance EKS
run: |
set +e
FAILED=0
# If inputs.delete does not equal '', set the IMAGE_TAG to the value of inputs.delete
if [ "${{ inputs.delete }}" != "" ]; then
IMAGE_TAG=${{ inputs.delete }}
else
IMAGE_TAG=${{ env.IMAGE_TAG }}
fi
INSTANCE=$(echo -n $IMAGE_TAG | md5sum | head -c 10)
# Delete Harbor image only on PR close / manual delete — not on ci:redeploy
DELETE_HARBOR=false
if [ "${{ github.event.action }}" = "closed" ] || [ "${{ inputs.delete }}" != "" ]; then
DELETE_HARBOR=true
fi
if kubectl get namespace/ci-$INSTANCE-ns-pm4 ; then
echo "Deleting Instance :: ci-$INSTANCE"
helm delete ci-$INSTANCE || true
kubectl delete namespace ci-$INSTANCE-ns-pm4 || true
#Drop database
deploy_db="pm4_ci-${INSTANCE}%"
deploy_ai="\`pm4_ci-$INSTANCE_ai\`"
# check that that string length of $deploy_db is 12 or more as a safety check. If its less than 12, exit now
if [ ${#deploy_db} -lt 12 ]; then
echo "ERROR: deploy_db safety check failed (length ${#deploy_db})"
exit 1
fi
# Drop the main database including any tenant databases.
# Continue on failure so a half-built instance still gets cleaned up.
set -o pipefail
mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -h ${{ secrets.RDS_ENG }} -N -e "SHOW DATABASES LIKE '${deploy_db}'" | xargs -r -I{} mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -h ${{ secrets.RDS_ENG }} -e "DROP DATABASE IF EXISTS \`{}\`;"
if [ $? -ne 0 ]; then
echo "WARN: Failed dropping databases matching ${deploy_db}"
FAILED=1
fi
set +o pipefail
mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -e "DROP DATABASE IF EXISTS $deploy_ai" -h ${{ secrets.RDS_ENG }}
if [ $? -ne 0 ]; then
echo "WARN: Failed dropping AI database $deploy_ai"
FAILED=1
fi
mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -e "DROP USER IF EXISTS 'user_ci-$INSTANCE'@'%'" -h ${{ secrets.RDS_ENG }}
if [ $? -ne 0 ]; then
echo "WARN: Failed dropping user user_ci-$INSTANCE"
FAILED=1
fi
mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -e "DROP USER IF EXISTS 'user_ci-$INSTANCE_ai'@'%'" -h ${{ secrets.RDS_ENG }}
if [ $? -ne 0 ]; then
echo "WARN: Failed dropping user user_ci-$INSTANCE_ai"
FAILED=1
fi
echo "The instance [https://ci-$INSTANCE.engk8s.processmaker.net] was deleted!!"
else
echo "The pull request does not have an instance on K8s [https://ci-$INSTANCE.engk8s.processmaker.net] not found!!"
fi
if [ "$DELETE_HARBOR" = "true" ]; then
echo "Deleting image from Harbor: ${IMAGE_TAG}"
HTTP_CODE=$(curl -s -o /tmp/harbor_delete_out -w "%{http_code}" -X DELETE \
-u ${{ secrets.REGISTRY_USERNAME }}:${{ secrets.REGISTRY_PASSWORD }} \
"https://${{ secrets.REGISTRY_HOST }}/api/v2.0/projects/processmaker/repositories/enterprise/artifacts/${IMAGE_TAG}")
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "Harbor image deleted (HTTP $HTTP_CODE)"
elif [ "$HTTP_CODE" = "404" ]; then
echo "Harbor image not found (already deleted)"
else
echo "WARN: Failed deleting Harbor image ${IMAGE_TAG} (HTTP $HTTP_CODE)"
cat /tmp/harbor_delete_out || true
FAILED=1
fi
else
echo "Skipping Harbor image delete (ci:redeploy)"
fi
if [ "$FAILED" -ne 0 ]; then
echo "One or more delete operations failed"
exit 1
fi
echo "Delete completed successfully"
# Strip ci:redeploy from the PR body so it does not re-trigger on later edits.
# Uses github.token (default) so this edit does not start a new workflow run.
# actions/github-script needs no gh binary on the self-hosted runner.
- name: Remove ci:redeploy from PR body
if: |
contains(github.event.pull_request.body, 'ci:redeploy') &&
github.event.action != 'closed' &&
inputs.delete == ''
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request.body || '';
if (!body.includes('ci:redeploy')) {
core.info('ci:redeploy tag not found in PR body; nothing to do');
return;
}
const newBody = body.replaceAll('ci:redeploy', '');
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
body: newBody,
});
core.info('Removed ci:redeploy from PR body');
runPhpUnit:
name: run-phpunit
if: |
!cancelled() &&
needs.imageEKS.result == 'success' &&
github.event.action != 'closed' &&
inputs.delete == ''
needs: imageEKS
runs-on: ${{ vars.RUNNER }}
timeout-minutes: 150
env:
PHPUNIT_CONTAINER: phpunit-ci-${{ github.run_id }}-${{ github.run_attempt }}
PHPUNIT_COMPOSE_DIR: pm4-k8s-distribution/images/pm4-tools
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: processmaker/.github
- name: Common
uses: ./.github/actions/common
with:
token: ${{ secrets.GIT_TOKEN }}
- name: Export Params
run: |
echo "IMAGE=${{ secrets.REGISTRY_HOST }}/processmaker/enterprise:${{env.IMAGE_TAG}}" >> $GITHUB_ENV
# - uses: actions/checkout@v2
# with:
# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
# - name: Clone repo K8S
# run: |
# echo "IMAGE: ${{ env.IMAGE }}"
# git clone --depth 1 -b "$K8S_BRANCH" "https://$GIT_TOKEN@github.com/ProcessMaker/pm4-k8s-distribution.git" pm4-k8s-distribution
- name: Login to Harbor
uses: docker/login-action@v2
with:
registry: ${{ secrets.REGISTRY_HOST }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: PHPUnits
run: |
set -uo pipefail
cd "$PHPUNIT_COMPOSE_DIR"
docker pull $IMAGE
docker compose down -v --remove-orphans --timeout 0
docker compose build phpunit
# Run detached instead of attached. An attached `docker compose run` swallows
# the runner's cancel signals: the container's PID 1 is sh, which ignores
# SIGTERM, so compose keeps waiting and the job hangs until GitHub gives up.
docker compose run --detach -T --name "$PHPUNIT_CONTAINER" phpunit
# SIGKILL the whole compose project so a cancel never waits on a stop timeout.
terminate() {
echo "Cancel requested ($1) - killing phpunit containers"
docker kill --signal KILL "$PHPUNIT_CONTAINER" || true
docker compose kill --signal KILL || true
exit 143
}
trap 'terminate SIGINT' INT
trap 'terminate SIGTERM' TERM
docker logs --follow "$PHPUNIT_CONTAINER" &
LOGS_PID=$!
# `docker wait` in the background keeps bash in its own interruptible `wait`,
# which is what lets the traps above fire immediately on cancel.
docker wait "$PHPUNIT_CONTAINER" > "$RUNNER_TEMP/phpunit-exit-code" &
wait $!
kill "$LOGS_PID" 2>/dev/null || true
PHPUNIT_EXIT=$(tr -dc '0-9' < "$RUNNER_TEMP/phpunit-exit-code")
echo "phpunit container exited with ${PHPUNIT_EXIT:-unknown}"
# docker cp works on the stopped container, docker exec does not.
echo "Copying coverage report from PHP Unit Container: $PHPUNIT_CONTAINER"
if docker cp "$PHPUNIT_CONTAINER:/opt/processmaker/coverage.xml" coverage.xml; then
echo "COVERAGE_EXISTS=true" >> $GITHUB_ENV
else
echo "coverage.xml not found in container, skipping coverage archive and SonarQube"
echo "COVERAGE_EXISTS=false" >> $GITHUB_ENV
fi
exit "${PHPUNIT_EXIT:-1}"
- name: Force cleanup phpunit containers
if: always()
run: |
set -u
docker rm --force --volumes "$PHPUNIT_CONTAINER" || true
cd "$PHPUNIT_COMPOSE_DIR" || exit 0
docker compose kill --signal KILL || true
docker compose down -v --remove-orphans --timeout 0 || true
- name: Archive code coverage
if: env.COVERAGE_EXISTS == 'true'
uses: actions/upload-artifact@v4
with:
name: code-coverage
path: ./pm4-k8s-distribution/images/pm4-tools/coverage.xml
- name: SonarQube Coverage Report
if: env.COVERAGE_EXISTS == 'true'
uses: sonarsource/sonarqube-scan-action@master
env:
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
with:
args: >
-Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }}
-Dsonar.sources=.
-Dsonar.tests=.
-Dsonar.test.inclusions=**/*Test.php
-Dsonar.php.coverage.reportPaths=./pm4-k8s-distribution/images/pm4-tools/coverage.xml