Skip to content

#28609 include in 23.10.24 LTS #9021

#28609 include in 23.10.24 LTS

#28609 include in 23.10.24 LTS #9021

name: Maven CICD Pipeline
on:
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
tags:
description: 'Tags'
required: false
push:
branches:
- master
- release-*
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}
cancel-in-progress: true
env:
JVM_TEST_MAVEN_OPTS: "-e -B --no-transfer-progress -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
DOCKER_PLATFORMS: "linux/amd64,linux/arm64"
REGISTRY: ghcr.io
IMAGE_NAME: dotcms/dotcms_test
jobs:
#
# Detect Code that has changed and create outputs to uses as conditionals
#
changes:
name: Check Changed Files
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
backend: ${{ steps.filter-rewrite.outputs.backend }}
frontend: ${{ steps.filter-rewrite.outputs.frontend }}
build: ${{ steps.filter-rewrite.outputs.build }}
jvm_unit_test: ${{ steps.filter-rewrite.outputs.jvm_unit_test }}
cli: ${{ steps.filter-rewrite.outputs.cli }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cleanup-runner
- uses: dorny/paths-filter@v2.11.1
id: filter
with:
filters: .github/filters.yaml
list-files: 'escape'
- name: Rewrite Filter
id: filter-rewrite
run: |
frontend=${{ steps.filter.outputs.frontend }}
cli=${{ steps.filter.outputs.cli }}
backend=${{ steps.filter.outputs.backend }}
build=${{ steps.filter.outputs.build }}
jvm_unit_test=${{ steps.filter.outputs.jvm_unit_test }}
is_backend=false
backend_files=( ${{ steps.filter.outputs.backend_files }} )
for backend_file in "${backend_files[@]}"
do
echo "Evaluating ${backend_file}"
if [[ ! ${backend_file} =~ ^dotCMS/src/main/webapp/html/.*\.(js|css)$ ]]; then
echo "Backend file ${backend_file} is not a frontend file... aborting search"
is_backend=true
break
fi
done
backend=${is_backend}
[[ "${backend}" == 'true' ]] && build=true && jvm_unit_test=true
echo "frontend=${frontend}"
echo "cli=${cli}"
echo "backend=${backend}"
echo "build=${build}"
echo "jvm_unit_test=${jvm_unit_test}"
echo "frontend=${frontend}" >> $GITHUB_OUTPUT
echo "cli=${cli}" >> $GITHUB_OUTPUT
echo "backend=${backend}" >> $GITHUB_OUTPUT
echo "build=${build}" >> $GITHUB_OUTPUT
echo "jvm_unit_test=${jvm_unit_test}" >> $GITHUB_OUTPUT
#
# Initial JDK 11 Build
# Basic build and install all with maven without running tests.
# Provides local maven repo for subsequent steps
#
build-jdk11:
name: "Initial JDK 11 Build"
runs-on: ubuntu-20.04
needs: changes
if: ${{ needs.changes.outputs.build == 'true' }}
outputs:
docker-tag: ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
env:
DOCKER_BUILD_CONTEXT: /home/runner/work/_temp/core-build
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cleanup-runner
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
# Docker setup
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0
with:
image: tonistiigi/binfmt:latest
platforms: ${{ env.DOCKER_PLATFORMS }}
- id: docker-setup-buildx
name: Docker Setup Buildx
uses: docker/setup-buildx-action@v3.0.0
with:
version: latest
platforms: ${{ env.DOCKER_PLATFORMS }}
driver-opts: |
image=moby/buildkit:v0.12.2
- name: Docker Hub login
uses: docker/login-action@v3.0.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- id: docker-login
name: Login to GHCR
uses: docker/login-action@v3.0.0
with:
registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,enable=true,priority=100,prefix=sha-,suffix=,format=short
type=raw,value=run-${{ github.run_id }}
- name: Debug docker metadata
run: |
echo "${{ fromJSON(steps.meta.outputs.json) }}"
shell: bash
- name: Get Date
id: get-date
run: |
echo "date=$(/bin/date -u "+%Y-%m")" >> $GITHUB_OUTPUT
shell: bash
- name: Cache Maven Repository
id: cache-maven
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: mavencore-${{ steps.get-date.outputs.date }}-${{ github.run_id }}
restore-keys: |
mavencore-${{ steps.get-date.outputs.date }}
- name: Cache Node Binary
id: cache-node-binary
uses: actions/cache@v3
with:
path: |
core-web/installs
key: node-binary-${{ hashFiles('core-web/.nvmrc') }}
- name: Cache NPM
id: cache-npm
uses: actions/cache@v3
with:
path: |
~/.npm
# if specific cache does not exist then can base upon latest version
key: npm-${{ hashFiles('core-web/package-lock.json') }}
restore-keys: npm-
- name: Maven Build No Test
shell: bash
env:
NODE_CACHE_HIT: ${{ steps.cache-node-modules.outputs.cache-hit }}
run: |
echo "Creating $DOCKER_BUILD_CONTEXT"
mkdir -p $DOCKER_BUILD_CONTEXT
./mvnw -Dprod=true $JVM_TEST_MAVEN_OPTS -Ddocker.buildArchiveOnly=$DOCKER_BUILD_CONTEXT -Dskip.npm.install=${NODE_CACHE_HIT} --show-version -DskipTests=true -DskipITs=true clean install --file pom.xml
- name: Setup Context
id: setup-docker-context
run: |
mkdir -p $DOCKER_BUILD_CONTEXT/context
tar -xvf $DOCKER_BUILD_CONTEXT/docker-build.tar -C $DOCKER_BUILD_CONTEXT/context
- name: Build base image
uses: docker/build-push-action@v5
with:
context: ${{ env.DOCKER_BUILD_CONTEXT }}/context
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms : ${{ env.DOCKER_PLATFORMS }}
pull: true
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Upload build reports
uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: "build-reports-Initial JDK 11 Build" #Should not be picked up for JunitReports
path: |
target/build-report.json
LICENSE
retention-days: 2
#
# Run all JVM Unit Tests in parallel with other tests
#
linux-jvm-tests:
name: JVM Unit Tests - JDK ${{matrix.java.name}}
runs-on: ubuntu-20.04
if: ${{ needs.changes.outputs.jvm_unit_test == 'true' }}
needs: [changes,build-jdk11]
timeout-minutes: 240
env:
MAVEN_OPTS: -Xmx2048m
strategy:
fail-fast: false
matrix:
java:
- {
name: "11",
java-version: 11,
distribution: 'temurin',
maven_args: ""
}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cleanup-runner
- name: Set up JDK ${{ matrix.java.name }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java.java-version }}
distribution: ${{ matrix.java.distribution }}
- name: Get Date
id: get-date
run: |
echo "date=$(/bin/date -u "+%Y-%m")" >> $GITHUB_OUTPUT
shell: bash
- name: Restore Maven Repository
id: cache-maven
uses: actions/cache/restore@v3
with:
path: ~/.m2/repository
key: mavencore-${{ steps.get-date.outputs.date }}-${{ github.run_id }}
- name: Build
# exclude core web we aare testing that separately
run: eval ./mvnw -Dprod $JVM_TEST_MAVEN_OPTS test -pl \!:dotcms-core-web ${{ matrix.java.maven_args}}
- name: Prepare reports archive (if maven failed)
if: failure()
shell: bash
run: find . -name '*-reports' -type d | tar -czf test-reports.tgz -T -
- name: Upload reports Archive (if maven failed)
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-reports-linux-jvm${{matrix.java.name}}
path: 'test-reports.tgz'
- name: core-maven-unit-tests
uses: actions/upload-artifact@v3
if: always()
with:
name: "build-reports-test-JVM Tests - JDK ${{matrix.java.name}}"
path: |
**/target/*-reports/TEST-*.xml
target/build-report.json
LICENSE
retention-days: 2
#
# Run Frontend Tests
#
linux-frontend-tests:
name: Frontend Unit Tests
runs-on: ubuntu-20.04
if: ${{ needs.changes.outputs.frontend == 'true' }}
needs: [changes,build-jdk11]
timeout-minutes: 240
env:
MAVEN_OPTS: -Xmx2048m
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cleanup-runner
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: 11
distribution: 'temurin'
- name: Get Date
id: get-date
run: |
echo "date=$(/bin/date -u "+%Y-%m")" >> $GITHUB_OUTPUT
shell: bash
- name: Restore Maven Repository
id: cache-maven
uses: actions/cache/restore@v3
with:
path: ~/.m2/repository
key: mavencore-${{ steps.get-date.outputs.date }}-${{ github.run_id }}
- name: Restore Node Binary
id: cache-node-binary
uses: actions/cache/restore@v3
with:
path: |
core-web/installs
key: node-binary-${{ hashFiles('core-web/.nvmrc') }}
- name: Restore Node
id: cache-npm
uses: actions/cache/restore@v3
with:
path: |
~/.npm
key: npm-${{ hashFiles('core-web/package-lock.json') }}
restore-keys: npm-
#- name: Restore Node Modules
# id: cache-node-modules
# uses: actions/cache/restore@v3
# with:
# path: core-web/node_modules
# # only pull for exact version of package-lock
# key: node-modules-${{ hashFiles('core-web/package-lock.json') }}
- name: Build # expect node cache to exist from
shell: bash
env:
NODE_CACHE_HIT: ${{ steps.cache-node-modules.outputs.cache-hit }}
run: eval ./mvnw -Dprod -Dskip.npm.install=${NODE_CACHE_HIT} $JVM_TEST_MAVEN_OPTS -pl :dotcms-core-web test
- name: Prepare failure archive (if maven failed)
if: failure()
shell: bash
run: find . -name 'surefire-reports' -type d | tar -czf test-reports.tgz -T -
- name: Upload failure Archive (if maven failed)
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-reports-frontend
path: 'test-reports.tgz'
- name: frontend-unit-tests
uses: actions/upload-artifact@v3
if: always()
with:
name: "build-reports-test-Frontend unit tests"
path: |
**/target/*-reports/**/TEST-*.xml
target/build-report.json
LICENSE
retention-days: 2
#
# Run Legacy Integration test suite batches
#
linux-integration-tests:
name: JVM IT Tests - JDK ${{matrix.java.name}} ${{matrix.suites.name}}
runs-on: ubuntu-20.04
# Skip master in forks
if: ${{ needs.changes.outputs.backend == 'true' }}
needs: [build-jdk11,changes]
timeout-minutes: 240
env:
MAVEN_OPTS: -Xmx2048m
strategy:
fail-fast: false
matrix:
java:
- {
name: "11",
java-version: 11,
distribution: 'temurin',
maven_args: ""
}
suites:
- {
name: "MainSuite 1a",
pathName: "mainsuite1a",
maven_args: '"-Dit.test=MainSuite1a" -Dit.test.forkcount=1'
}
- {
name: "MainSuite 1b",
pathName: "mainsuite1b",
maven_args: '"-Dit.test=MainSuite1b" -Dit.test.forkcount=1'
}
- {
name: "MainSuite 2a",
pathName: "mainsuite2a",
maven_args: '"-Dit.test=MainSuite2a" -Dit.test.forkcount=1'
}
- {
name: "MainSuite 2b",
pathName: "mainsuite2b",
maven_args: '"-Dit.test=MainSuite2b" -Dit.test.forkcount=1'
}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cleanup-runner
- name: Set up IT Tests ${{ matrix.java.name }} ${{ matrix.suites.name }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java.java-version }}
distribution: ${{ matrix.java.distribution }}
#- name: Download Maven Repo
# uses: actions/download-artifact@v3
# with:
# name: maven-repo
# path: .
#- name: Extract Maven Repo
# shell: bash
# run: tar -xzf maven-repo.tgz -C ~
- name: Get Date
id: get-date
run: |
echo "date=$(/bin/date -u "+%Y-%m")" >> $GITHUB_OUTPUT
shell: bash
- name: Restore Maven Repository
id: cache-maven
uses: actions/cache/restore@v3
with:
path: ~/.m2/repository
key: mavencore-${{ steps.get-date.outputs.date }}-${{ github.run_id }}
- name: Build
env:
DOT_DOTCMS_LICENSE: ${{ secrets.DOTCMS_LICENSE }}
run: eval ./mvnw -Dprod $JVM_TEST_MAVEN_OPTS verify -pl :dotcms-integration ${{ matrix.suites.maven_args}} ${{ matrix.java.maven_args}}
- name: Prepare reports archive (if maven failed)
if: failure()
shell: bash
run: find . -name '*-reports' -type d | tar -czf test-reports.tgz -T -
- name: Upload failure Archive (if maven failed)
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-reports-linux-jvm${{matrix.java.name}}-${{matrix.suites.pathName}}
path: 'test-reports.tgz'
- name: failsafe-it-tests # Uploads will be merged with same name
uses: actions/upload-artifact@v3
if: always()
with:
name: "build-reports-test-IT Tests - JDK ${{matrix.java.name}} - ${{matrix.suites.name}}"
path: |
**/target/*-reports/TEST-*.xml
target/build-report.json
LICENSE
retention-days: 2
linux-postman-tests:
name: Run Postman Tests - ${{matrix.collection_group}}
runs-on: ubuntu-latest
needs: [build-jdk11, changes]
if: ${{ needs.changes.outputs.backend == 'true' }}
strategy:
fail-fast: false
matrix:
collection_group: [ 'category-container', 'content', 'experiment', 'graphql', 'page', 'pp', 'template', 'workflow', 'default' ]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cleanup-runner
- run: sudo npm install --location=global newman@5.3.2 newman-reporter-htmlextra@1.23.0
- id: docker-login
name: Login to GHCR
uses: docker/login-action@v3.0.0
with:
registry: ghcr.io
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.GHCR_TOKEN }}
- id: resolve-postman-collections
uses: ./.github/actions/resolve-postman-collections
with:
current: ${{ matrix.collection_group }}
- name: Prepare license
id: prepare-license
run: |
postman_dir=${{ github.workspace }}/dotcms-postman
docker_volumes_dir=${postman_dir}/target/docker-volumes
[[ -d ${docker_volumes_dir} ]] && rm -rf ${docker_volumes_dir}
dotcms_volumes_dir=${docker_volumes_dir}/dotcms
license_dir=${dotcms_volumes_dir}/license
mkdir -p ${license_dir}
license_file=${license_dir}/license.dat
touch ${license_file}
echo "${{ secrets.DOTCMS_LICENSE }}" > ${license_file}
chmod -R 777 ${license_dir}
- id: run-postman-tests
name: Run Postman Tests
timeout-minutes: 90
run: |
./mvnw -Dpostman-tests -Dprod $JVM_TEST_MAVEN_OPTS verify \
-pl :dotcms-postman \
-Dpostman.collections="${{ steps.resolve-postman-collections.outputs.collections_to_run }}"
pec=$?
[[ pec -eq 0 ]] || exit pec
env:
DOTCMS_IMAGE: ${{ needs.build-jdk11.outputs.docker-tag }}
- name: Prepare reports archive (if maven failed)
if: failure()
shell: bash
run: |
find . -name '*-reports' -type d
tree ./dotcms-postman
find . -name '*-reports' -type d | tar -czf test-reports.tgz -T -
- name: Upload failure Archive (if maven failed)
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-reports-postman-${{ matrix.collection_group }}
path: 'test-reports.tgz'
- name: failsafe-postman-tests # Uploads will be merged with same name
uses: actions/upload-artifact@v3
if: always()
with:
name: "build-reports-test-postman - ${{ matrix.collection_group }}"
path: |
**/dotcms-postman/target/postman-reports/TEST-*.xml
target/build-report.json
LICENSE
retention-days: 2
clean-up:
name: Clean up
runs-on: ubuntu-latest
needs: [ build-jdk11, linux-postman-tests ]
if: (success() || failure()) && (github.repository == 'dotCMS/core' || !endsWith(github.ref, '/master'))
steps:
- uses: actions/checkout@v4
- id: cleanup-package
name: Clean up CICD packages
uses: ./.github/actions/cleanup-gh-packages
with:
delete_tags: ${{ needs.build-jdk11.outputs.docker-tag }}
access_token: ${{ secrets.GHCR_TOKEN }}
#
# Collate reports and build status
#
prepare-report-data:
name: Prepare Report Data
runs-on: ubuntu-20.04
needs: [ build-jdk11,linux-jvm-tests,linux-integration-tests,linux-frontend-tests,linux-postman-tests ]
if: always()
outputs:
aggregate_status: ${{ steps.prepare-workflow-data.outputs.aggregate_status }}
steps:
- name: Download build reports
uses: actions/download-artifact@v3
with:
path: /tmp/build-step-reports
- name: Prepare workflow data
id: prepare-workflow-data
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
AGGREGATE_STATUS="SUCCESS"
jobs_status="${{ toJson(needs) }}"
echo "job status=${jobs_status}"
while IFS=" " read -r key result; do
key=$(echo $key | tr -d ' {')
result=$(echo $result | tr -d ',')
echo "Job: $key, Result: $result"
if [[ $result == "cancelled" ]]; then
AGGREGATE_STATUS="CANCELLED"
break
elif [[ $result == "failure" ]]; then
AGGREGATE_STATUS="FAILURE"
fi
done < <(echo "$jobs_status" | awk -F': ' '/result:/ {print $1,$2}')
FIRST_FAIL_STEP=""
FIRST_FAIL_MODULE=""
echo '{' > workflow-data.json
EVENT_TYPE="${{ github.event_name }}"
if [[ "$EVENT_TYPE" == "pull_request" ]]; then
echo "Creating workflow data for pull request ${PR_TITLE}"
BRANCH="${{ github.head_ref }}"
else
PR_TITLE="N/A"
BRANCH="${{ github.ref }}"
echo "Creating workflow data for branch ${BRANCH}"
fi
BRANCH="${BRANCH##*/}"
PR_TITLE_JQ=$(jq --arg title "$PR_TITLE" -n '$title')
echo '"branch": "'$BRANCH'",' >> workflow-data.json
echo '"run_id": "'${{ github.run_id }}'",' >> workflow-data.json
echo '"trigger_event_name": "'$GITHUB_EVENT_NAME'",' >> workflow-data.json
echo '"source_repository": "'$GITHUB_REPOSITORY'",' >> workflow-data.json
echo '"merge_sha": "'${{ github.sha }}'",' >> workflow-data.json
echo '"base_sha": "'${{ github.event.pull_request.base.sha }}'",' >> workflow-data.json
echo '"base_branch": "'${{ github.event.pull_request.base.sha }}'",' >> workflow-data.json
echo '"base_author": "'${{ github.event.pull_request.base.user.login }}'",' >> workflow-data.json
echo '"head_author": "'${{ github.event.pull_request.head.user.login }}'",' >> workflow-data.json
echo '"head_name": "'${{ github.event.pull_request.head.ref }}'",' >> workflow-data.json
echo '"head_sha": "'${{ github.event.pull_request.head.sha }}'",' >> workflow-data.json
echo '"pr_id": "'${{ github.event.pull_request.id }}'",' >> workflow-data.json
echo '"pr_number": "'${{ github.event.pull_request.number }}'",' >> workflow-data.json
echo "\"pr_title\": $PR_TITLE_JQ," >> workflow-data.json
echo '"pr_author": "'${{ github.event.pull_request.user.login }}'",' >> workflow-data.json
echo '"pr_merge_state": "'${{ github.event.pull_request.mergeable_state }}'",' >> workflow-data.json
echo '"build_reports": [' >> workflow-data.json
total_reports=$(find /tmp/build-step-reports/build-reports-*/target -name build-report.json 2>/dev/null | wc -l)
report_index=0
if [ "$total_reports" -eq "0" ]; then
echo "No build report files found."
else
for build_report in "/tmp/build-step-reports/build-reports-"*/target/build-report.json; do
((report_index=report_index+1))
step_name=$(basename "$(dirname "$(dirname "$build_report")")" | sed 's/build-reports-//')
cat "$build_report" | jq ".step_name = \"$step_name\"" >> workflow-data.json
# If the aggregate status is still SUCCESS, check if this module failed
if [[ "$AGGREGATE_STATUS" == "SUCCESS" ]]; then
# Loop over each projectReport
length=$(jq '.projectReports | length' "$build_report")
for (( i=0; i<$length; i++ )); do
status=$(jq -r ".projectReports[$i].status" "$build_report")
if [[ "$status" == "FAILURE" ]]; then
AGGREGATE_STATUS="FAILURE"
FIRST_FAIL_STEP="$step_name"
FIRST_FAIL_MODULE="$(jq -r ".projectReports[$i].name" "$build_report")"
FIRST_FAIL_ERROR="$(jq -r ".projectReports[$i].error" "$build_report")"
fi
done
fi
# If not the last file, append a comma
if (( report_index != total_reports )); then
echo ',' >> workflow-data.json
fi
done
fi
echo '],' >> workflow-data.json
echo '"aggregate_status": "'$AGGREGATE_STATUS'"' >> workflow-data.json
if [[ "$AGGREGATE_STATUS" != "SUCCESS" ]]; then
echo ',' >> workflow-data.json
echo '"first_fail_step": "'$FIRST_FAIL_STEP'",' >> workflow-data.json
echo '"first_fail_module": "'$FIRST_FAIL_MODULE'",' >> workflow-data.json
echo '"first_fail_error": "'$FIRST_FAIL_ERROR'"' >> workflow-data.json
fi
echo '}' >> workflow-data.json
echo "aggregate_status=${AGGREGATE_STATUS}" >> $GITHUB_OUTPUT
- name: Upload workflow data
uses: actions/upload-artifact@v3
with:
name: workflow-data
path: ./workflow-data.json
final-status:
name: Final Status
needs: prepare-report-data
if: always()
runs-on: ubuntu-20.04
steps:
- name: Check Final Status
run: |
if [ "${{ needs.prepare-report-data.outputs.aggregate_status }}" != "SUCCESS" ]; then
echo "One or more jobs failed!"
exit 1
fi