nolanpro send deploy EKS 🚀 #333
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: 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 |