Kookster310 send deploy EKS 🚀 #199
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 }} | |
| # K8S_BRANCH: ${{ contains(github.event.pull_request.body, 'ci:next') && 'next' || 'release-2024-fall' }} | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ inputs.delete }} | |
| cancel-in-progress: true | |
| jobs: | |
| imageEKS: | |
| name: build-docker-image-EKS | |
| if: 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 ===" | |
| imageEKSBase: | |
| name: build-docker-image-EKS-base | |
| if: github.event.action != 'closed' && inputs.delete == '' && contains(github.event.pull_request.body, 'ci:performance-tests') | |
| 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 base branch and image tag | |
| run: | | |
| BASE_BRANCH="${{ github.event.pull_request.base.ref || github.event.repository.default_branch || 'develop' }}" | |
| BASE_IMAGE_TAG="processmaker-$(echo "${BASE_BRANCH}" | sed 's;/;-;g')" | |
| echo "BASE_BRANCH=${BASE_BRANCH}" >> $GITHUB_ENV | |
| echo "BASE_IMAGE_TAG=${BASE_IMAGE_TAG}" >> $GITHUB_ENV | |
| echo "Base branch: ${BASE_BRANCH} -> image tag: ${BASE_IMAGE_TAG}" | |
| - name: Generate image EKS (base) | |
| if: env.BASE_BRANCH != 'develop' && !contains(github.event.pull_request.body, 'ci:skip-build') | |
| run: | | |
| cd pm4-k8s-distribution/images | |
| export CI_RELEASE_BRANCH=$RELEASE_BRANCH | |
| branch="${{ env.BASE_BRANCH }}" tag="${{ env.BASE_IMAGE_TAG }}" bash build.k8s-cicd.sh | |
| echo "VERSION=${{ env.BASE_IMAGE_TAG }}" >> $GITHUB_ENV | |
| - name: List Images | |
| if: env.BASE_BRANCH != 'develop' && !contains(github.event.pull_request.body, 'ci:skip-build') | |
| run: docker images | |
| - name: Login to Harbor | |
| if: env.BASE_BRANCH != 'develop' && !contains(github.event.pull_request.body, 'ci:skip-build') | |
| uses: docker/login-action@v2 | |
| with: | |
| registry: ${{ secrets.REGISTRY_HOST }} | |
| username: ${{ secrets.REGISTRY_USERNAME }} | |
| password: ${{ secrets.REGISTRY_PASSWORD }} | |
| - name: Push Enterprise Image to Harbor (base) | |
| if: env.BASE_BRANCH != 'develop' && !contains(github.event.pull_request.body, 'ci:skip-build') | |
| run: | | |
| docker tag processmaker/enterprise:${{ env.BASE_IMAGE_TAG }} ${{ secrets.REGISTRY_HOST }}/processmaker/enterprise:${{ env.BASE_IMAGE_TAG }} | |
| docker push ${{ secrets.REGISTRY_HOST }}/processmaker/enterprise:${{ env.BASE_IMAGE_TAG }} | |
| - name: Base image ready | |
| if: env.BASE_BRANCH == 'develop' || contains(github.event.pull_request.body, 'ci:skip-build') | |
| run: | | |
| echo "Skipped base build (base is develop or ci:skip-build); baseline will use existing image if needed." | |
| deployEKS: | |
| name: deploy-EKS | |
| if: contains(github.event.pull_request.body, 'ci:deploy') | |
| 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" | |
| 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#{{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 | |
| 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: 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}" | |
| deleteEKS: | |
| name: Delete Instance | |
| if: github.event.action == 'closed' || inputs.delete != '' | |
| runs-on: self-hosted | |
| steps: | |
| - name: Delete instance EKS | |
| run: | | |
| # 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) | |
| if kubectl get namespace/ci-$INSTANCE-ns-pm4 ; then | |
| echo "Deleting Instace :: ci-$INSTANCE" | |
| helm delete ci-$INSTANCE | |
| kubectl delete namespace ci-$INSTANCE-ns-pm4 | |
| #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 | |
| exit 1 | |
| fi | |
| # Drop the main database including any tenant databases | |
| mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -h ${{ secrets.RDS_ENG }} -N -e "SHOW DATABASES LIKE '${deploy_db}'" | xargs -I{} mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -h ${{ secrets.RDS_ENG }} -e "DROP DATABASE IF EXISTS \`{}\`;" | |
| mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -e "DROP DATABASE IF EXISTS $deploy_ai" -h ${{ secrets.RDS_ENG }} | |
| mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -e "DROP USER IF EXISTS 'user_ci-$INSTANCE'@'%'" -h ${{ secrets.RDS_ENG }} | |
| mysql -u${{ secrets.USER_MYSQL_ENG }} -p${{ secrets.PASS_MYSQL_ENG }} -e "DROP USER IF EXISTS 'user_ci-$INSTANCE_ai'@'%'" -h ${{ secrets.RDS_ENG }} | |
| #Drop image Harbor | |
| curl -X DELETE -u ${{ secrets.REGISTRY_USERNAME }}:${{ secrets.REGISTRY_PASSWORD }} "https://${{ secrets.REGISTRY_HOST }}/api/v2.0/projects/processmaker/repositories/enterprise/artifacts/${IMAGE_TAG}" | |
| 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 | |
| # Performance test releases (safety net if perf job was cancelled). Perf installs in default namespace. | |
| # Release names include 8-char hex suffix: ci-${INSTANCE}-perf-baseline-<hex8>, ci-${INSTANCE}-perf-update-<hex8> | |
| for release in $(helm list -n default -q 2>/dev/null | grep -E "^ci-${INSTANCE}-perf-(baseline|update)-[a-f0-9]{8}$" || true); do | |
| echo "Uninstalling performance release: $release (default namespace)" | |
| helm uninstall "$release" --namespace default 2>/dev/null || true | |
| ns="${release}-ns-pm4" | |
| if kubectl get namespace "$ns" &>/dev/null; then | |
| echo "Deleting performance namespace: $ns" | |
| kubectl delete namespace "$ns" --timeout=120s --ignore-not-found=true || true | |
| fi | |
| done | |
| perfBaseline: | |
| name: perf-baseline | |
| if: github.event.action != 'closed' && inputs.delete == '' && contains(github.event.pull_request.body, 'ci:performance-tests') | |
| needs: [imageEKS, imageEKSBase] | |
| runs-on: ${{ vars.RUNNER }} | |
| steps: | |
| - name: Checkout .github repo | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: processmaker/.github | |
| ref: automated-performance-tests | |
| - 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_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/ | |
| - name: Authenticate with Amazon EKS | |
| run: aws eks update-kubeconfig --region us-east-1 --name pm4-eng | |
| - name: Prepare performance test (baseline) | |
| id: perf_prep | |
| run: | | |
| INSTANCE=$(echo -n ${{ env.IMAGE_TAG }} | md5sum | head -c 10) | |
| RANDOM_SUFFIX=$(openssl rand -hex 4) | |
| echo "instance=${INSTANCE}" >> $GITHUB_OUTPUT | |
| echo "INSTANCE=${INSTANCE}" >> $GITHUB_ENV | |
| echo "random_suffix=${RANDOM_SUFFIX}" >> $GITHUB_OUTPUT | |
| echo "RANDOM_SUFFIX=${RANDOM_SUFFIX}" >> $GITHUB_ENV | |
| echo "Instance ID: ${INSTANCE} (hostname suffix: ${RANDOM_SUFFIX})" | |
| - name: Substitute perf instance template (baseline) | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| BASE_BRANCH="${{ github.event.pull_request.base.ref || github.event.repository.default_branch || 'develop' }}" | |
| BASE_IMAGE_TAG="processmaker-$(echo "${BASE_BRANCH}" | sed 's;/;-;g')" | |
| PERF_NODE_LABEL="ci-${INSTANCE}-baseline" | |
| echo "BASE_BRANCH=${BASE_BRANCH}" >> $GITHUB_ENV | |
| echo "BASE_IMAGE_TAG=${BASE_IMAGE_TAG}" >> $GITHUB_ENV | |
| sed -i "s#{{INSTANCE}}#${INSTANCE}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{PERF_NODE_LABEL}}#${PERF_NODE_LABEL}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{APP_VERSION}}#${BASE_IMAGE_TAG}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{CUSTOMER_LICENSES_PAT}}#${{ secrets.CUSTOMER_LICENSES_PAT }}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{KEYCLOAK_CLIENT_SECRET}}#${{ secrets.KEYCLOAK_CLIENT_SECRET }}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{KEYCLOAK_PASSWORD}}#${{ secrets.KEYCLOAK_PASSWORD }}#g" .github/templates/instance-perf.yaml | |
| - name: Create performance node group (baseline) | |
| run: | | |
| export INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| export PERF_SUFFIX=baseline | |
| chmod +x .github/scripts/create-perf-nodegroup.sh | |
| .github/scripts/create-perf-nodegroup.sh | |
| - name: Wait for performance node Ready (baseline) | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| echo "Waiting for node with label performance=ci-${INSTANCE}-baseline to be Ready..." | |
| for i in $(seq 1 30); do | |
| READY=$(kubectl get nodes -l "performance=ci-${INSTANCE}-baseline" -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true) | |
| if [ "$READY" = "True" ]; then | |
| echo "Node is Ready." | |
| break | |
| fi | |
| echo "Waiting... ($i/30)" | |
| sleep 20 | |
| done | |
| kubectl get nodes -l "performance=ci-${INSTANCE}-baseline" | |
| - name: Checkout automated-performance-metrics | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ProcessMaker/automated-performance-metrics | |
| ref: main | |
| path: automated-performance-metrics | |
| token: ${{ secrets.GIT_TOKEN }} | |
| - name: Install k6 | |
| run: | | |
| sudo gpg -k | |
| sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 | |
| echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list | |
| sudo apt-get update | |
| sudo apt-get install -y k6 | |
| - name: Discover k6 tests | |
| run: | | |
| find automated-performance-metrics/scripts -name '*.js' -type f | sort > perf-test-scripts.txt | |
| if [ ! -s perf-test-scripts.txt ]; then | |
| echo "automated-performance-metrics/scripts/Api/users-index.js" > perf-test-scripts.txt | |
| fi | |
| echo "Discovered k6 tests:" | |
| cat perf-test-scripts.txt | |
| - name: Deploy baseline (base branch) and verify | |
| id: baseline | |
| env: | |
| IMAGE_TAG: ${{ env.IMAGE_TAG }} | |
| HELM_REPO: ${{ secrets.HELM_REPO }} | |
| HELM_USERNAME: ${{ secrets.HELM_USERNAME }} | |
| HELM_PASSWORD: ${{ secrets.HELM_PASSWORD }} | |
| OPENAI_API_KEY: ${{ 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 }} | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-baseline-${RANDOM_SUFFIX}" | |
| export RELEASE_NAME APP_VERSION="${{ env.BASE_IMAGE_TAG }}" INSTANCE | |
| export versionHelm="${{ env.versionHelm }}" | |
| chmod +x .github/scripts/deploy-perf-instance.sh | |
| .github/scripts/deploy-perf-instance.sh | |
| BASE_URL="https://${RELEASE_NAME}.engk8s.processmaker.net" | |
| echo "Checking $BASE_URL/login ..." | |
| for i in $(seq 1 30); do | |
| CODE=$(curl -s -o /dev/null -w "%{http_code}" -k "$BASE_URL/login" || echo 000) | |
| if [ "$CODE" = "200" ]; then | |
| echo "Baseline /login returned 200." | |
| break | |
| fi | |
| echo "Attempt $i: got $CODE" | |
| sleep 15 | |
| done | |
| CODE=$(curl -s -o /dev/null -w "%{http_code}" -k "$BASE_URL/login") | |
| if [ "$CODE" != "200" ]; then | |
| echo "Baseline /login did not return 200 (got $CODE). Continuing to cleanup." | |
| fi | |
| - name: Wait for TLS certificate (baseline) | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-baseline-${RANDOM_SUFFIX}" | |
| APP_NS="${RELEASE_NAME}-ns-pm4" | |
| CERT_NAME="${RELEASE_NAME}-tls-secret" | |
| echo "Waiting for cert-manager to issue TLS certificate for ${RELEASE_NAME}..." | |
| READY="" | |
| for i in $(seq 1 5); do | |
| READY=$(kubectl get certificate -n "$APP_NS" -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | tr ' ' '\n' | head -1) | |
| if [ "$READY" = "True" ]; then | |
| echo "TLS certificate Ready." | |
| break | |
| fi | |
| echo "Waiting for TLS certificate... ($i/5)" | |
| sleep 10 | |
| done | |
| if [ "$READY" != "True" ]; then | |
| echo "Certificate not ready after 5 loops; deleting to trigger regeneration..." | |
| kubectl delete certificate -n "$APP_NS" "$CERT_NAME" --ignore-not-found=true || true | |
| sleep 5 | |
| echo "Waiting up to 5 more loops for renewed certificate..." | |
| for i in $(seq 1 5); do | |
| READY=$(kubectl get certificate -n "$APP_NS" -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | tr ' ' '\n' | head -1) | |
| if [ "$READY" = "True" ]; then | |
| echo "TLS certificate Ready (after retry)." | |
| break | |
| fi | |
| echo "Waiting for TLS certificate (retry)... ($i/5)" | |
| sleep 10 | |
| done | |
| fi | |
| if [ "$READY" != "True" ]; then | |
| echo "TLS certificate did not become Ready; k6 may see certificate errors." | |
| fi | |
| - name: Generate admin access token (baseline) | |
| id: token_baseline | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-baseline-${RANDOM_SUFFIX}" | |
| APP_NS="${RELEASE_NAME}-ns-pm4" | |
| POD="" | |
| attempts=0 | |
| while [ -z "$POD" ]; do | |
| POD=$(kubectl get pods -n "$APP_NS" -l service=web --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) | |
| [ -n "$POD" ] && break | |
| attempts=$((attempts + 1)) | |
| [ $attempts -ge 5 ] && break | |
| sleep 10 | |
| done | |
| echo "Using web pod: ${POD:-none}" | |
| TOKEN=$(kubectl exec -n "$APP_NS" "$POD" -- sudo -u nginx php /opt/processmaker/artisan processmaker:generate-access-token admin 2>/dev/null | tr -d '\r\n') || true | |
| if [ -z "$TOKEN" ]; then | |
| echo "Could not generate baseline token" | |
| echo "token=" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "token=${TOKEN}" >> $GITHUB_OUTPUT | |
| - name: Run k6 on baseline | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-baseline-${RANDOM_SUFFIX}" | |
| APP_URL="https://${RELEASE_NAME}.engk8s.processmaker.net" | |
| TOKEN="${{ steps.token_baseline.outputs.token }}" | |
| if [ -z "$TOKEN" ]; then | |
| echo "Skipping baseline k6: no token" | |
| exit 0 | |
| fi | |
| export BASE_PATH="${APP_URL}" | |
| export BEARER_TOKEN="Bearer ${TOKEN}" | |
| while IFS= read -r script; do | |
| [ -z "$script" ] && continue | |
| test_id=$(echo "$script" | sed 's|automated-performance-metrics/scripts/||;s|\.js$||;s|/|-|g') | |
| echo "Running baseline k6: $script (id: $test_id)" | |
| k6 run "$script" 2>&1 | tee "baseline-${test_id}-k6-results.txt" || true | |
| done < perf-test-scripts.txt | |
| - name: Upload baseline results | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: baseline-results | |
| path: | | |
| perf-test-scripts.txt | |
| baseline-*-k6-results.txt | |
| - name: Cleanup baseline | |
| if: always() | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-baseline-${RANDOM_SUFFIX}" | |
| APP_NS="${RELEASE_NAME}-ns-pm4" | |
| helm uninstall "${RELEASE_NAME}" --namespace default 2>/dev/null || true | |
| kubectl delete namespace "${APP_NS}" --timeout=120s --ignore-not-found=true || true | |
| - name: Delete performance node group (baseline) | |
| if: always() | |
| run: | | |
| export INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| export PERF_SUFFIX=baseline | |
| chmod +x .github/scripts/delete-perf-nodegroup.sh | |
| .github/scripts/delete-perf-nodegroup.sh | |
| perfUpdate: | |
| name: perf-update | |
| if: github.event.action != 'closed' && inputs.delete == '' && contains(github.event.pull_request.body, 'ci:performance-tests') | |
| needs: [imageEKS, imageEKSBase] | |
| runs-on: ${{ vars.RUNNER }} | |
| steps: | |
| - name: Checkout .github repo | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: processmaker/.github | |
| ref: automated-performance-tests | |
| - 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_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/ | |
| - name: Authenticate with Amazon EKS | |
| run: aws eks update-kubeconfig --region us-east-1 --name pm4-eng | |
| - name: Prepare performance test (update) | |
| id: perf_prep | |
| run: | | |
| INSTANCE=$(echo -n ${{ env.IMAGE_TAG }} | md5sum | head -c 10) | |
| RANDOM_SUFFIX=$(openssl rand -hex 4) | |
| echo "instance=${INSTANCE}" >> $GITHUB_OUTPUT | |
| echo "INSTANCE=${INSTANCE}" >> $GITHUB_ENV | |
| echo "random_suffix=${RANDOM_SUFFIX}" >> $GITHUB_OUTPUT | |
| echo "RANDOM_SUFFIX=${RANDOM_SUFFIX}" >> $GITHUB_ENV | |
| echo "Instance ID: ${INSTANCE} (hostname suffix: ${RANDOM_SUFFIX})" | |
| - name: Substitute perf instance template (update) | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| PERF_NODE_LABEL="ci-${INSTANCE}-update" | |
| sed -i "s#{{INSTANCE}}#${INSTANCE}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{PERF_NODE_LABEL}}#${PERF_NODE_LABEL}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{APP_VERSION}}#${{ env.IMAGE_TAG }}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{CUSTOMER_LICENSES_PAT}}#${{ secrets.CUSTOMER_LICENSES_PAT }}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{KEYCLOAK_CLIENT_SECRET}}#${{ secrets.KEYCLOAK_CLIENT_SECRET }}#g" .github/templates/instance-perf.yaml | |
| sed -i "s#{{KEYCLOAK_PASSWORD}}#${{ secrets.KEYCLOAK_PASSWORD }}#g" .github/templates/instance-perf.yaml | |
| - name: Create performance node group (update) | |
| run: | | |
| export INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| export PERF_SUFFIX=update | |
| chmod +x .github/scripts/create-perf-nodegroup.sh | |
| .github/scripts/create-perf-nodegroup.sh | |
| - name: Wait for performance node Ready (update) | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| echo "Waiting for node with label performance=ci-${INSTANCE}-update to be Ready..." | |
| for i in $(seq 1 30); do | |
| READY=$(kubectl get nodes -l "performance=ci-${INSTANCE}-update" -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true) | |
| if [ "$READY" = "True" ]; then | |
| echo "Node is Ready." | |
| break | |
| fi | |
| echo "Waiting... ($i/30)" | |
| sleep 20 | |
| done | |
| kubectl get nodes -l "performance=ci-${INSTANCE}-update" | |
| - name: Checkout automated-performance-metrics | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ProcessMaker/automated-performance-metrics | |
| ref: main | |
| path: automated-performance-metrics | |
| token: ${{ secrets.GIT_TOKEN }} | |
| - name: Install k6 | |
| run: | | |
| sudo gpg -k | |
| sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 | |
| echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list | |
| sudo apt-get update | |
| sudo apt-get install -y k6 | |
| - name: Discover k6 tests | |
| run: | | |
| find automated-performance-metrics/scripts -name '*.js' -type f | sort > perf-test-scripts.txt | |
| if [ ! -s perf-test-scripts.txt ]; then | |
| echo "automated-performance-metrics/scripts/Api/users-index.js" > perf-test-scripts.txt | |
| fi | |
| echo "Discovered k6 tests:" | |
| cat perf-test-scripts.txt | |
| - name: Deploy update (PR build) and verify | |
| id: update | |
| env: | |
| IMAGE_TAG: ${{ env.IMAGE_TAG }} | |
| HELM_REPO: ${{ secrets.HELM_REPO }} | |
| HELM_USERNAME: ${{ secrets.HELM_USERNAME }} | |
| HELM_PASSWORD: ${{ secrets.HELM_PASSWORD }} | |
| OPENAI_API_KEY: ${{ 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 }} | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-update-${RANDOM_SUFFIX}" | |
| export RELEASE_NAME APP_VERSION="${{ env.IMAGE_TAG }}" INSTANCE | |
| export versionHelm="${{ env.versionHelm }}" | |
| .github/scripts/deploy-perf-instance.sh | |
| BASE_URL="https://${RELEASE_NAME}.engk8s.processmaker.net" | |
| echo "Checking $BASE_URL/login ..." | |
| for i in $(seq 1 30); do | |
| CODE=$(curl -s -o /dev/null -w "%{http_code}" -k "$BASE_URL/login" || echo 000) | |
| if [ "$CODE" = "200" ]; then | |
| echo "Update /login returned 200." | |
| break | |
| fi | |
| echo "Attempt $i: got $CODE" | |
| sleep 15 | |
| done | |
| CODE=$(curl -s -o /dev/null -w "%{http_code}" -k "$BASE_URL/login") | |
| if [ "$CODE" != "200" ]; then | |
| echo "Update /login did not return 200 (got $CODE). Continuing to cleanup." | |
| fi | |
| - name: Wait for TLS certificate (update) | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-update-${RANDOM_SUFFIX}" | |
| APP_NS="${RELEASE_NAME}-ns-pm4" | |
| CERT_NAME="${RELEASE_NAME}-tls-secret" | |
| echo "Waiting for cert-manager to issue TLS certificate for ${RELEASE_NAME}..." | |
| READY="" | |
| for i in $(seq 1 5); do | |
| READY=$(kubectl get certificate -n "$APP_NS" -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | tr ' ' '\n' | head -1) | |
| if [ "$READY" = "True" ]; then | |
| echo "TLS certificate Ready." | |
| break | |
| fi | |
| echo "Waiting for TLS certificate... ($i/5)" | |
| sleep 10 | |
| done | |
| if [ "$READY" != "True" ]; then | |
| echo "Certificate not ready after 5 loops; deleting to trigger regeneration..." | |
| kubectl delete certificate -n "$APP_NS" "$CERT_NAME" --ignore-not-found=true || true | |
| sleep 5 | |
| echo "Waiting up to 5 more loops for renewed certificate..." | |
| for i in $(seq 1 5); do | |
| READY=$(kubectl get certificate -n "$APP_NS" -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | tr ' ' '\n' | head -1) | |
| if [ "$READY" = "True" ]; then | |
| echo "TLS certificate Ready (after retry)." | |
| break | |
| fi | |
| echo "Waiting for TLS certificate (retry)... ($i/5)" | |
| sleep 10 | |
| done | |
| fi | |
| if [ "$READY" != "True" ]; then | |
| echo "TLS certificate did not become Ready; k6 may see certificate errors." | |
| fi | |
| - name: Generate admin access token (update instance) | |
| id: token | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-update-${RANDOM_SUFFIX}" | |
| APP_NS="${RELEASE_NAME}-ns-pm4" | |
| POD="" | |
| attempts=0 | |
| while [ -z "$POD" ]; do | |
| POD=$(kubectl get pods -n "$APP_NS" -l service=web --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) | |
| [ -n "$POD" ] && break | |
| attempts=$((attempts + 1)) | |
| [ $attempts -ge 5 ] && break | |
| sleep 10 | |
| done | |
| echo "Using web pod: ${POD:-none}" | |
| TOKEN=$(kubectl exec -n "$APP_NS" "$POD" -- sudo -u nginx php /opt/processmaker/artisan processmaker:generate-access-token admin 2>/dev/null | tr -d '\r\n') || true | |
| if [ -z "$TOKEN" ]; then | |
| echo "Could not generate token (pod may still be starting)" | |
| echo "token=" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "token=${TOKEN}" >> $GITHUB_OUTPUT | |
| - name: Run k6 on update | |
| id: k6_update | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-update-${RANDOM_SUFFIX}" | |
| APP_URL="https://${RELEASE_NAME}.engk8s.processmaker.net" | |
| TOKEN="${{ steps.token.outputs.token }}" | |
| if [ -z "$TOKEN" ]; then | |
| echo "Skipping update k6: no token" | |
| exit 0 | |
| fi | |
| export BASE_PATH="${APP_URL}" | |
| export BEARER_TOKEN="Bearer ${TOKEN}" | |
| while IFS= read -r script; do | |
| [ -z "$script" ] && continue | |
| test_id=$(echo "$script" | sed 's|automated-performance-metrics/scripts/||;s|\.js$||;s|/|-|g') | |
| echo "Running update k6: $script (id: $test_id)" | |
| k6 run "$script" 2>&1 | tee "update-${test_id}-k6-results.txt" || true | |
| done < perf-test-scripts.txt | |
| - name: Upload update results | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: update-results | |
| path: | | |
| perf-test-scripts.txt | |
| update-*-k6-results.txt | |
| - name: Cleanup update | |
| if: always() | |
| run: | | |
| INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| RANDOM_SUFFIX="${{ env.RANDOM_SUFFIX }}" | |
| RELEASE_NAME="ci-${INSTANCE}-perf-update-${RANDOM_SUFFIX}" | |
| APP_NS="${RELEASE_NAME}-ns-pm4" | |
| helm uninstall "${RELEASE_NAME}" --namespace default 2>/dev/null || true | |
| kubectl delete namespace "${APP_NS}" --timeout=120s --ignore-not-found=true || true | |
| - name: Delete performance node group (update) | |
| if: always() | |
| run: | | |
| export INSTANCE="${{ steps.perf_prep.outputs.instance }}" | |
| export PERF_SUFFIX=update | |
| chmod +x .github/scripts/delete-perf-nodegroup.sh | |
| .github/scripts/delete-perf-nodegroup.sh | |
| perfComment: | |
| name: perf-comment | |
| if: github.event.action != 'closed' && inputs.delete == '' && contains(github.event.pull_request.body, 'ci:performance-tests') | |
| needs: [perfBaseline, perfUpdate] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download baseline results | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: baseline-results | |
| - name: Download update results | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: update-results | |
| - name: Prepare performance comment body | |
| run: | | |
| cp baseline-results/perf-test-scripts.txt . 2>/dev/null || cp update-results/perf-test-scripts.txt . | |
| cp baseline-results/baseline-*-k6-results.txt . 2>/dev/null || true | |
| cp update-results/update-*-k6-results.txt . 2>/dev/null || true | |
| extract_summary() { | |
| sed -n '/█ TOTAL RESULTS\|TOTAL RESULTS/,/^running (0m/p' "$1" 2>/dev/null | sed '/^running (0m/d' || true | |
| } | |
| get_metric() { grep -E "^\s+$1\.+" "$2" 2>/dev/null | head -1 | awk '{ for(i=2;i<=NF;i++) printf "%s%s", $i, (i<NF?" ":""); print "" }' | xargs; } | |
| get_http_duration_avg() { grep -E "^\s+http_req_duration" "$1" 2>/dev/null | head -1 | sed -n 's/.*avg=\([0-9.]*\)ms.*/\1/p'; } | |
| get_http_failed_pct() { grep -E "^\s+http_req_failed" "$1" 2>/dev/null | head -1 | sed -n 's/.*: \([0-9.]*\)%.*/\1/p'; } | |
| BASE_BRANCH="${{ github.event.pull_request.base.ref || github.event.repository.default_branch || 'develop' }}" | |
| { | |
| echo '✅ **Performance tests** completed.' | |
| echo '' | |
| echo "**Baseline:** \`${BASE_BRANCH}\` | **Update:** PR build (\`${{ env.IMAGE_TAG }}\`)" | |
| echo '' | |
| } > perf-comment.md | |
| while IFS= read -r script; do | |
| [ -z "$script" ] && continue | |
| test_id=$(echo "$script" | sed 's|automated-performance-metrics/scripts/||;s|\.js$||;s|/|-|g') | |
| BASE_FILE="baseline-${test_id}-k6-results.txt" | |
| UPD_FILE="update-${test_id}-k6-results.txt" | |
| base_iter="$(get_metric 'iterations' "$BASE_FILE" 2>/dev/null || echo '—')" | |
| upd_iter="$(get_metric 'iterations' "$UPD_FILE" 2>/dev/null || echo '—')" | |
| base_dur="$(get_http_duration_avg "$BASE_FILE" 2>/dev/null || echo '—')" | |
| upd_dur="$(get_http_duration_avg "$UPD_FILE" 2>/dev/null || echo '—')" | |
| base_fail="$(get_http_failed_pct "$BASE_FILE" 2>/dev/null || echo '—')" | |
| upd_fail="$(get_http_failed_pct "$UPD_FILE" 2>/dev/null || echo '—')" | |
| { | |
| echo "### Comparison (\`${test_id}\`)" | |
| echo '' | |
| echo '| Metric | Baseline | Update |' | |
| echo '|--------|----------|--------|' | |
| echo "| iterations | \`${base_iter}\` | \`${upd_iter}\` |" | |
| echo "| http_req_duration (avg ms) | \`${base_dur}\` | \`${upd_dur}\` |" | |
| echo "| http_req_failed (%) | \`${base_fail}\` | \`${upd_fail}\` |" | |
| echo '' | |
| echo "### k6 summary — baseline (\`${test_id}\`)" | |
| echo '```' | |
| extract_summary "$BASE_FILE" | cat -s 2>/dev/null || echo '(no summary)' | |
| echo '```' | |
| echo '' | |
| echo "### k6 summary — update (\`${test_id}\`)" | |
| echo '```' | |
| extract_summary "$UPD_FILE" | cat -s 2>/dev/null || echo '(no summary)' | |
| echo '```' | |
| echo '' | |
| } >> perf-comment.md | |
| done < perf-test-scripts.txt | |
| - name: Comment PR (performance results) | |
| if: success() && github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const body = fs.existsSync('perf-comment.md') | |
| ? fs.readFileSync('perf-comment.md', 'utf8') | |
| : '✅ **Performance tests** completed (k6 results not captured).'; | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body | |
| }); | |
| runPhpUnit: | |
| name: run-phpunit | |
| if: github.event.action != 'closed' && inputs.delete == '' | |
| needs: imageEKS | |
| 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: 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: | | |
| cd pm4-k8s-distribution/images/pm4-tools | |
| docker pull $IMAGE | |
| docker compose down -v | |
| docker compose build phpunit | |
| docker compose run phpunit | |
| CONTAINER_ID=$(sudo docker ps -a | grep phpunit | awk '{print $1}') | |
| echo "Copying coverage report from PHP Unit Container: $CONTAINER_ID" | |
| if sudo docker exec $CONTAINER_ID test -f /opt/processmaker/coverage.xml; then | |
| sudo docker cp $CONTAINER_ID:/opt/processmaker/coverage.xml coverage.xml | |
| 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 | |
| - 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 |