name: Build and Test Workflow on: workflow_dispatch: # push: # branches: ["main"] pull_request: branches: ["main"] # cancel in-progress jobs if a new job is triggered # This is useful to avoid running multiple builds for the same branch if a new commit is pushed # or a pull request is updated. # It helps to save resources and time by ensuring that only the latest commit is built and tested # This is particularly useful for long-running jobs that may take a while to complete. # The `group` is set to a combination of the workflow name, event name, and branch name. # This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of # in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened. concurrency: group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }} cancel-in-progress: true permissions: contents: read jobs: files-changed: name: detect what files changed runs-on: ubuntu-latest timeout-minutes: 3 # Map a step output to a job output outputs: build: ${{ steps.changes.outputs.build }} app: ${{ steps.changes.outputs.app }} project: ${{ steps.changes.outputs.project }} openapi: ${{ steps.changes.outputs.openapi }} docker: ${{ steps.changes.outputs.docker }} steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Check for file changes uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 id: changes with: filters: ".github/config/.files.yaml" build: runs-on: ubuntu-latest permissions: actions: read security-events: write strategy: fail-fast: false matrix: jdk-version: [17, 21] spring-security: [true, false] steps: - name: Harden Runner uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK ${{ matrix.jdk-version }} uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: ${{ matrix.jdk-version }} distribution: "temurin" - name: Setup Gradle uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 with: gradle-version: 8.14 - name: Build with Gradle and spring security ${{ matrix.spring-security }} run: gradle clean build -x spotlessApply -x spotlessCheck env: DISABLE_ADDITIONAL_FEATURES: ${{ matrix.spring-security }} - name: Check Test Reports Exist id: check-reports if: always() run: | declare -a dirs=( "app/core/build/reports/tests/" "app/core/build/test-results/" "app/common/build/reports/tests/" "app/common/build/test-results/" "app/proprietary/build/reports/tests/" "app/proprietary/build/test-results/" ) missing_reports=() for dir in "${dirs[@]}"; do if [ ! -d "$dir" ]; then missing_reports+=("$dir") fi done if [ ${#missing_reports[@]} -gt 0 ]; then echo "ERROR: The following required test report directories are missing:" printf '%s\n' "${missing_reports[@]}" exit 1 fi echo "All required test report directories are present" - name: Upload Test Reports if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }} path: | app/core/build/reports/jacoco/test app/core/build/reports/tests/ app/core/build/test-results/ app/core/build/reports/problems/ app/common/build/reports/tests/ app/common/build/test-results/ app/common/build/reports/jacoco/test app/common/build/reports/problems/ app/proprietary/build/reports/tests/ app/proprietary/build/test-results/ app/proprietary/build/reports/jacoco/test app/proprietary/build/reports/problems/ build/reports/problems/ retention-days: 3 if-no-files-found: warn - name: Add coverage to PR with spring security ${{ matrix.spring-security }} and JDK ${{ matrix.jdk-version }} id: jacoco uses: madrapps/jacoco-report@50d3aff4548aa991e6753342d9ba291084e63848 # v1.7.2 with: paths: | ${{ github.workspace }}/**/build/reports/jacoco/test/jacocoTestReport.xml token: ${{ secrets.GITHUB_TOKEN }} min-coverage-overall: 10 min-coverage-changed-files: 0 comment-type: summary check-generateOpenApiDocs: if: needs.files-changed.outputs.openapi == 'true' needs: [files-changed, build] runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK 17 uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: "17" distribution: "temurin" - name: Setup Gradle uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - name: Generate OpenAPI documentation run: gradle :stirling-pdf:generateOpenApiDocs env: DISABLE_ADDITIONAL_FEATURES: true - name: Upload OpenAPI Documentation uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: openapi-docs path: ./SwaggerDoc.json check-licence: if: needs.files-changed.outputs.build == 'true' needs: [files-changed, build] runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK 17 uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: "17" distribution: "temurin" - name: Check licenses for compatibility run: gradle clean checkLicense env: DISABLE_ADDITIONAL_FEATURES: false STIRLING_PDF_DESKTOP_UI: true - name: FAILED - Check licenses for compatibility if: failure() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: dependencies-without-allowed-license.json path: | build/reports/dependency-license/dependencies-without-allowed-license.json retention-days: 3 docker-compose-tests: if: needs.files-changed.outputs.project != 'true' needs: files-changed # if: github.event_name == 'push' && github.ref == 'refs/heads/main' || # (github.event_name == 'pull_request' && # contains(github.event.pull_request.labels.*.name, 'licenses') == false && # ( # contains(github.event.pull_request.labels.*.name, 'Front End') || # contains(github.event.pull_request.labels.*.name, 'Java') || # contains(github.event.pull_request.labels.*.name, 'Back End') || # contains(github.event.pull_request.labels.*.name, 'Security') || # contains(github.event.pull_request.labels.*.name, 'API') || # contains(github.event.pull_request.labels.*.name, 'Docker') || # contains(github.event.pull_request.labels.*.name, 'Test') # ) # ) runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Java 17 uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: "17" distribution: "temurin" - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Install Docker Compose run: | sudo curl -SL "https://github.com/docker/compose/releases/download/v2.40.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: "3.12" cache: "pip" # caching pip dependencies cache-dependency-path: ./testing/cucumber/requirements.txt - name: Pip requirements run: | pip install --require-hashes -r ./testing/cucumber/requirements.txt - name: Run Docker Compose Tests run: | chmod +x ./testing/test_webpages.sh chmod +x ./testing/test.sh chmod +x ./testing/test_disabledEndpoints.sh ./testing/test.sh test-build-docker-images: if: (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') && needs.files-changed.outputs.docker == 'true' needs: [files-changed, build, docker-compose-tests] # tmp disable runs-on: ubuntu-latest strategy: fail-fast: false matrix: # docker-rev: ["Dockerfile", "Dockerfile.ultra-lite", "Dockerfile.fat"] # temp disable docker-rev: ["Dockerfile.fat"] steps: - name: Harden Runner uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK 17 uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: "17" distribution: "temurin" - name: Set up Gradle uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 with: gradle-version: 8.14 - name: Build application run: gradle clean build -x spotlessApply -x spotlessCheck env: DISABLE_ADDITIONAL_FEATURES: true STIRLING_PDF_DESKTOP_UI: false - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 with: platforms: linux/amd64,linux/arm64/v8 - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 with: platforms: linux/amd64,linux/arm64/v8 - name: Convert repository owner to lowercase id: repoowner run: echo "lowercase=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT - name: Generate safe test tag id: meta run: | SAFE_NAME=$(echo "${{ matrix.docker-rev }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9.-]/-/g') TAG="ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:${SAFE_NAME}-${{ github.sha }}" echo "tags=$TAG" >> $GITHUB_OUTPUT echo "Building and testing: $TAG" - name: Build and load amd64 image uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: builder: ${{ steps.buildx.outputs.name }} context: . file: ./${{ matrix.docker-rev }} push: false load: true cache-from: type=gha cache-to: type=gha,mode=max tags: ${{ steps.meta.outputs.tags }} platforms: linux/amd64 provenance: false sbom: false - name: Test Java works (amd64) run: docker run --rm ${{ steps.meta.outputs.tags }} java -version - name: Prune amd64 image and cache if: always() run: | docker image rm -f ${{ steps.meta.outputs.tags }} || true docker builder prune --force || true - name: Build and load arm64 image uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: builder: ${{ steps.buildx.outputs.name }} context: . file: ./${{ matrix.docker-rev }} push: false load: true cache-from: type=gha cache-to: type=gha,mode=max tags: ${{ steps.meta.outputs.tags }} platforms: linux/arm64/v8 provenance: false sbom: false - name: Test Java works (arm64) run: docker run --rm --platform linux/arm64 ${{ steps.meta.outputs.tags }} java -version - name: Test app.jar exists (arm64) run: | docker run --rm --platform linux/arm64 ${{ steps.meta.outputs.tags }} \ sh -c "test -f /app.jar && echo 'app.jar OK' || (echo 'ERROR: app.jar missing!' && exit 1)" - name: Deep arm64 tests (fat image only) if: matrix.docker-rev == 'Dockerfile.fat' run: | TAG="${{ steps.meta.outputs.tags }}" echo "Running deep tests on arm64 for fat image..." # LibreOffice binary linkage docker run --rm --platform linux/arm64 $TAG \ sh -c "ldd /usr/lib/libreoffice/program/soffice.bin > /dev/null && echo 'LibreOffice binary linked OK'" || \ (echo "LibreOffice binary broken on arm64!" && exit 1) # Python uno import docker run --rm --platform linux/arm64 $TAG \ sh -c "python3 -c 'import uno; print(\"uno import OK\")'" || \ (echo "Python uno import failed on arm64!" && exit 1) # Full startup TAG="${{ steps.meta.outputs.tags }}" echo "Running quick startup check on arm64..." docker run --rm --platform linux/arm64 \ $TAG \ sh -c "java -jar /app.jar --version || java -jar /app.jar --help || java -jar /app.jar" > startup.log 2>&1 & # Warte max. 90 Sekunden und prüfe, ob die Startzeile mit Version erscheint timeout 90 bash -c " until grep -q 'Starting SPDFApplication v' startup.log; do sleep 2 if ! kill -0 \$! 2>/dev/null; then echo 'Java process died!' cat startup.log exit 1 fi done echo 'Stirling-PDF fat started successfully on arm64!' " || (echo "Startup failed:"; cat startup.log; exit 1) - name: Cleanup arm64 image and cache if: always() run: | docker image rm -f ${{ steps.meta.outputs.tags }} || true docker builder prune --force || true - name: Upload Docker build reports if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: reports-docker-${{ matrix.docker-rev }} path: | build/reports/ build/test-results/ build/reports/problems/ retention-days: 3 if-no-files-found: warn