From 84d236afd4039a926ce8b1f5f9e511b46c7fbf85 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 12 Nov 2025 10:58:00 +0100 Subject: [PATCH 01/12] IONOS(build): copy build-artifact.yml as build-artifact-original.yml for later performance comparison Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact-original.yml | 924 ++++++++++++++++++ 1 file changed, 924 insertions(+) create mode 100644 .github/workflows/build-artifact-original.yml diff --git a/.github/workflows/build-artifact-original.yml b/.github/workflows/build-artifact-original.yml new file mode 100644 index 0000000000000..a2cbfdb849649 --- /dev/null +++ b/.github/workflows/build-artifact-original.yml @@ -0,0 +1,924 @@ +name: Build Nextcloud Workspace artifact + +# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2025 STRATO AG +# SPDX-License-Identifier: AGPL-3.0-or-later + +# The Nextcloud Workspace source is packaged as a container image. +# This is a workaround because releases cannot be created without tags, +# and we want to be able to create snapshots from branches. + +on: + pull_request: + paths: + - '.github/workflows/**' + - 'src/**' + - 'apps/**' + - 'apps/**/appinfo/info.xml' + - 'apps-external/**' + - 'IONOS' + - 'package.json' + - 'package-lock.json' + - 'themes/**' + - 'lib/**' + - 'tsconfig.json' + - '**.js' + - '**.ts' + - '**.vue' + - '.gitmodules' + push: + branches: + - ionos-dev + - ionos-stable + +concurrency: + group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/ionos-dev' && github.run_id || github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + TARGET_PACKAGE_NAME: nc-workspace.zip + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + ARTIFACTORY_REPOSITORY_SNAPSHOT: ionos-productivity-ncwserver-snapshot + +permissions: + contents: read + +jobs: + prepare-matrix: + runs-on: ubuntu-latest + outputs: + external-apps-matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + submodules: true + fetch-depth: '1' + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y make jq + + - name: Set matrix + id: set-matrix + run: | + # Create matrix configuration as a compact JSON string + matrix='[ + { + "name": "activity", + "path": "apps-external/activity", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_activity_app" + }, + { + "name": "assistant", + "path": "apps-external/assistant", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_assistant_app" + }, + { + "name": "calendar", + "path": "apps-external/calendar", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_calendar_app" + }, + { + "name": "circles", + "path": "apps-external/circles", + "has_npm": false, + "has_composer": true, + "makefile_target": "build_circles_app" + }, + { + "name": "collectives", + "path": "apps-external/collectives", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_collectives_app" + }, + { + "name": "contacts", + "path": "apps-external/contacts", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_contacts_app" + }, + { + "name": "deck", + "path": "apps-external/deck", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_deck_app" + }, + { + "name": "end_to_end_encryption", + "path": "apps-external/end_to_end_encryption", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_end_to_end_encryption_app" + }, + { + "name": "forms", + "path": "apps-external/forms", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_forms_app" + }, + { + "name": "groupfolders", + "path": "apps-external/groupfolders", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_groupfolders_app" + }, + { + "name": "integration_openai", + "path": "apps-external/integration_openai", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_integration_openai_app" + }, + { + "name": "mail", + "path": "apps-external/mail", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_mail_app" + }, + { + "name": "ncw_apps_menu", + "path": "apps-external/ncw_apps_menu", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_ncw_apps_menu_app" + }, + { + "name": "ncw_mailtemplate", + "path": "apps-external/ncw_mailtemplate", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_ncw_mailtemplate_app" + }, + { + "name": "notes", + "path": "apps-external/notes", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_notes_app" + }, + { + "name": "notifications", + "path": "apps-external/notifications", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_notifications_app" + }, + { + "name": "notify_push", + "path": "apps-external/notify_push", + "has_npm": false, + "has_composer": true, + "makefile_target": "build_notify_push_app" + }, + { + "name": "password_policy", + "path": "apps-external/password_policy", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_password_policy_app" + }, + { + "name": "richdocuments", + "path": "apps-external/richdocuments", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_richdocuments_app" + }, + { + "name": "spreed", + "path": "apps-external/spreed", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_spreed_app" + }, + { + "name": "tables", + "path": "apps-external/tables", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_tables_app" + }, + { + "name": "tasks", + "path": "apps-external/tasks", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_tasks_app" + }, + { + "name": "text", + "path": "apps-external/text", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_text_app" + }, + { + "name": "twofactor_totp", + "path": "apps-external/twofactor_totp", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_twofactor_totp_app" + }, + { + "name": "user_oidc", + "path": "apps-external/user_oidc", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_user_oidc_app" + }, + { + "name": "viewer", + "path": "apps-external/viewer", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_viewer_app" + }, + { + "name": "whiteboard", + "path": "apps-external/whiteboard", + "has_npm": true, + "has_composer": true, + "makefile_target": "build_whiteboard_app" + } + ]' + + # Validate JSON and output as compact format + if echo "$matrix" | jq empty 2>/dev/null; then + echo "matrix=$(echo "$matrix" | jq -c '.')" >> $GITHUB_OUTPUT + echo "Matrix configuration set successfully" + else + echo "Error: Invalid JSON in matrix configuration" + exit 1 + fi + + - name: Validate matrix against Makefile + run: | + set +e # Intentionally allow script to continue on error for custom error handling and reporting to GITHUB_STEP_SUMMARY + set -u # Exit on undefined variable + + echo "### 🔍 Matrix Validation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Debug: Check if apps-external exists + echo "Checking apps-external directory..." + if [ ! -d "apps-external" ]; then + echo "❌ **Error:** apps-external directory does not exist!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Directory listing:" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + ls -la >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + echo "Apps-external directory exists. Listing contents:" + ls -la apps-external/ | head -10 + + # Check if jq is available + echo "Checking if jq is installed..." + if ! command -v jq &> /dev/null; then + echo "❌ **Error:** jq is not installed!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "jq is required for matrix generation but was not found in PATH." >> $GITHUB_STEP_SUMMARY + exit 1 + fi + echo "jq version: $(jq --version)" + + echo "Generating matrix from Makefile..." + # Capture both stdout and stderr separately to better diagnose issues + makefile_output=$(make -f IONOS/Makefile generate_external_apps_matrix_json 2>&1) + makefile_exit_code=$? + + echo "Makefile exit code: ${makefile_exit_code}" + echo "Makefile output length: ${#makefile_output}" + + # Debug: Check if GITHUB_STEP_SUMMARY is set + echo "GITHUB_STEP_SUMMARY: ${GITHUB_STEP_SUMMARY:-NOT SET}" + + # If the Makefile command failed, show the error + if [ ${makefile_exit_code} -ne 0 ]; then + echo "" + echo "=== MAKEFILE ERROR ===" + echo "Exit code: ${makefile_exit_code}" + echo "Output:" + echo "$makefile_output" + echo "=====================" + echo "" + + # Write to summary + echo "❌ **Error:** Makefile command failed with exit code ${makefile_exit_code}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + echo "Makefile error output" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "$makefile_output" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + + echo "Error written to summary file: ${GITHUB_STEP_SUMMARY}" + exit 1 + fi + + # Filter out the info message to get just the JSON + # The Makefile outputs "[i] Generating..." to stderr, but we captured everything with 2>&1 + # So we need to extract just the JSON part + generated_matrix=$(echo "$makefile_output" | grep -v '^\[i\]' || echo "$makefile_output") + + workflow_matrix='${{ steps.set-matrix.outputs.matrix }}' + + # Debug output + echo "Generated matrix length: ${#generated_matrix}" + echo "Workflow matrix length: ${#workflow_matrix}" + + # Show first 200 chars of generated matrix for debugging + if [ -n "$generated_matrix" ]; then + echo "Generated matrix preview: ${generated_matrix:0:200}..." + fi + + # Validate that we got valid JSON + if ! echo "$generated_matrix" | jq empty 2>/dev/null; then + echo "❌ **Error:** Generated matrix is not valid JSON" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + echo "Invalid JSON output" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "$generated_matrix" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + # Validate that we got data + if [ -z "$generated_matrix" ] || [ -z "$workflow_matrix" ]; then + echo "❌ **Error:** Failed to load matrices" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- Generated matrix empty: $([ -z "$generated_matrix" ] && echo "yes" || echo "no")" >> $GITHUB_STEP_SUMMARY + echo "- Workflow matrix empty: $([ -z "$workflow_matrix" ] && echo "yes" || echo "no")" >> $GITHUB_STEP_SUMMARY + echo "- Makefile exit code: ${makefile_exit_code}" >> $GITHUB_STEP_SUMMARY + + exit 1 + fi + + # Sort both matrices for comparison + generated_sorted=$(echo "$generated_matrix" | jq -S '.' 2>&1 || echo "ERROR") + workflow_sorted=$(echo "$workflow_matrix" | jq -S '.' 2>&1 || echo "ERROR") + + echo "Sorted matrix lengths - generated: ${#generated_sorted}, workflow: ${#workflow_sorted}" + + # Compare the two matrices + if [ "$generated_sorted" = "$workflow_sorted" ]; then + echo "✅ **Validation passed!** The workflow matrix matches the Makefile configuration." >> $GITHUB_STEP_SUMMARY + echo "" + echo "✅ Matrix validation passed!" + else + echo "❌ **Validation failed!** The workflow matrix does not match the Makefile configuration." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + echo "Starting detailed comparison..." + + # Extract app names from both matrices + generated_apps=$(echo "$generated_matrix" | jq -r '.[].name' 2>/dev/null | sort || echo "") + workflow_apps=$(echo "$workflow_matrix" | jq -r '.[].name' 2>/dev/null | sort || echo "") + + echo "Generated apps count: $(echo "$generated_apps" | wc -l)" + echo "Workflow apps count: $(echo "$workflow_apps" | wc -l)" + + # Find missing apps (in Makefile but not in workflow) + missing_apps=$(comm -23 <(echo "$generated_apps") <(echo "$workflow_apps")) + if [ $? -ne 0 ]; then + echo "Error: comm command failed when finding missing apps." >&2 + exit 1 + fi + # Find extra apps (in workflow but not in Makefile) + extra_apps=$(comm -13 <(echo "$generated_apps") <(echo "$workflow_apps")) + if [ $? -ne 0 ]; then + echo "Error: comm command failed when finding extra apps." >&2 + exit 1 + fi + + echo "Missing apps: ${missing_apps:-none}" + echo "Extra apps: ${extra_apps:-none}" + + if [ -n "$missing_apps" ]; then + echo "#### ⚠️ Missing Apps" >> $GITHUB_STEP_SUMMARY + echo "The following apps are configured in the Makefile but missing from the workflow:" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "$missing_apps" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + if [ -n "$extra_apps" ]; then + echo "#### ⚠️ Extra Apps" >> $GITHUB_STEP_SUMMARY + echo "The following apps are in the workflow but not configured in the Makefile:" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "$extra_apps" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + # Check for configuration mismatches in common apps + common_apps=$(comm -12 <(echo "$generated_apps") <(echo "$workflow_apps") 2>/dev/null || echo "") + + echo "Common apps count: $(echo "$common_apps" | wc -l)" + + if [ -n "$common_apps" ]; then + mismatched_apps="" + + while IFS= read -r app; do + [ -z "$app" ] && continue + gen_config=$(echo "$generated_matrix" | jq -c --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "") + wf_config=$(echo "$workflow_matrix" | jq -c --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "") + + if [ -n "$gen_config" ] && [ -n "$wf_config" ] && [ "$gen_config" != "$wf_config" ]; then + mismatched_apps="${mismatched_apps}${app}"$'\n' + fi + done <<< "$common_apps" + + echo "Mismatched apps: ${mismatched_apps:-none}" + + if [ -n "$mismatched_apps" ]; then + echo "#### ⚠️ Configuration Mismatches" >> $GITHUB_STEP_SUMMARY + echo "The following apps have different configurations (has_npm, has_composer, etc.):" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "$mismatched_apps" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + echo "📋 Detailed differences" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```diff' >> $GITHUB_STEP_SUMMARY + + while IFS= read -r app; do + [ -z "$app" ] && continue + echo "=== $app ===" >> $GITHUB_STEP_SUMMARY + diff -u --label "Workflow" --label "Makefile" \ + <(echo "$workflow_matrix" | jq --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "{}") \ + <(echo "$generated_matrix" | jq --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "{}") \ + >> $GITHUB_STEP_SUMMARY 2>&1 || true + done <<< "$mismatched_apps" + + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + fi + + # Provide fix instructions + echo "#### 🔧 How to Fix" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Run this command locally to generate the correct matrix:" >> $GITHUB_STEP_SUMMARY + echo '```bash' >> $GITHUB_STEP_SUMMARY + echo "make -f IONOS/Makefile generate_external_apps_matrix_json" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Then update the \`matrix\` variable in \`.github/workflows/build-artifact.yml\` in the set-matrix step with the generated output." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Show full diff in expandable section + echo "
" >> $GITHUB_STEP_SUMMARY + echo "📄 Full matrix comparison" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Workflow Matrix:**" >> $GITHUB_STEP_SUMMARY + echo '```json' >> $GITHUB_STEP_SUMMARY + echo "$workflow_matrix" | jq '.' 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "$workflow_matrix" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Makefile Matrix:**" >> $GITHUB_STEP_SUMMARY + echo '```json' >> $GITHUB_STEP_SUMMARY + echo "$generated_matrix" | jq '.' 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "$generated_matrix" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + + echo "" + echo "❌ ERROR: Matrix validation failed!" + echo "See the job summary for details on what's wrong and how to fix it." + echo "Summary file size: $(wc -c < $GITHUB_STEP_SUMMARY || echo 0) bytes" + exit 1 + fi + + build-external-apps: + runs-on: ubuntu-latest + needs: prepare-matrix + + permissions: + contents: read + + name: build-external-apps + strategy: + max-parallel: 20 + matrix: + app: ${{ fromJson(needs.prepare-matrix.outputs.external-apps-matrix) }} + + steps: + - name: Checkout server + uses: actions/checkout@v5 + with: + submodules: true + fetch-depth: '1' + + - name: Set up node with version from package.json's engines + if: matrix.app.has_npm + uses: actions/setup-node@v5 + with: + node-version-file: "package.json" + cache: 'npm' + cache-dependency-path: ${{ matrix.app.path }}/package-lock.json + + - name: Setup PHP with PECL extension + if: matrix.app.has_composer + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 #v2.31.1 + with: + tools: composer:v2 + extensions: gd, zip, curl, xml, xmlrpc, mbstring, sqlite, xdebug, pgsql, intl, imagick, gmp, apcu, bcmath, redis, soap, imap, opcache + env: + runner: ubuntu-latest + + - name: Cache Composer dependencies for ${{ matrix.app.name }} + if: matrix.app.has_composer + uses: actions/cache@v4 + with: + path: ${{ matrix.app.path }}/vendor + key: ${{ runner.os }}-composer-${{ matrix.app.name }}-${{ hashFiles(format('{0}/composer.lock', matrix.app.path)) }} + restore-keys: | + ${{ runner.os }}-composer-${{ matrix.app.name }}- + + - name: Build ${{ matrix.app.name }} app + run: make -f IONOS/Makefile ${{ matrix.app.makefile_target }} + + - name: Upload ${{ matrix.app.name }} build artifacts + uses: actions/upload-artifact@v4 + with: + retention-days: 1 + name: external-app-build-${{ matrix.app.name }} + path: | + ${{ matrix.app.path }} + !${{ matrix.app.path }}/node_modules + + build-artifact: + runs-on: ubuntu-latest + needs: [prepare-matrix, build-external-apps] + + permissions: + contents: read + + outputs: + NC_VERSION: ${{ steps.get_nc_version.outputs.NC_VERSION }} + + name: build-artifact + steps: + - name: Checkout server + uses: actions/checkout@v5 + with: + submodules: true + fetch-depth: '1' + + - name: Download build external apps + uses: actions/download-artifact@v5 + with: + pattern: external-app-build-* + path: apps-external/ + + - name: Reorganize downloaded apps-external artifacts + run: | + cd apps-external/ + + echo "Initial structure:" + ls -la + + # Move contents from external-app-build-* directories to their target directories + for artifact_dir in external-app-build-*; do + if [ -d "$artifact_dir" ]; then + # Extract app name from artifact directory name + app_name=${artifact_dir#external-app-build-} + + echo "Processing artifact: $artifact_dir -> $app_name" + + # If target directory exists, merge the contents from the artifact directory containing build artifacts + if [ -d "$app_name" ]; then + echo "Target directory $app_name exists, merging contents from $artifact_dir" + # Copy contents from artifact directory to target directory + cp -r "$artifact_dir"/* "$app_name"/ + # Remove the now-empty artifact directory + rm -rf "$artifact_dir" + else + # Move the artifact directory to the proper app name + echo "Moving $artifact_dir to $app_name" + mv "$artifact_dir" "$app_name" + fi + fi + done + + echo "Reorganization complete. Final structure:" + ls -la + + - name: Verify downloaded artifacts structure + run: | + echo "External apps structure:" + ls -la apps-external/ + for app_dir in apps-external/*/; do + if [ -d "$app_dir" ]; then + echo "Contents of $app_dir:" + ls -la "$app_dir" + fi + done + + - name: Set up node with version from package.json's engines + uses: actions/setup-node@v5 + with: + node-version-file: "package.json" + cache: 'npm' + + - name: Install Dependencies + run: sudo apt-get update && sudo apt-get install -y make zip unzip + + - name: Print dependencies versions + run: make --version && node --version && npm --version + + - name: Setup PHP with PECL extension + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 #v2.31.1 + with: + tools: composer:v2 + extensions: gd, zip, curl, xml, xmlrpc, mbstring, sqlite, xdebug, pgsql, intl, imagick, gmp, apcu, bcmath, redis, soap, imap, opcache + env: + runner: ubuntu-latest + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: vendor + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Print PHP install + run: php -i && php -m + + - name: Build Nextcloud + run: make -f IONOS/Makefile build_ncw + + - name: Add config partials + run: make -f IONOS/Makefile add_config_partials + + - name: Zip dependencies + run: make -f IONOS/Makefile zip_dependencies TARGET_PACKAGE_NAME=${{ env.TARGET_PACKAGE_NAME }} + + - name: Get NC version + id: get_nc_version + continue-on-error: false + run: | + NC_VERSION=$(jq -r '.ncVersion' version.json) + echo "NC_VERSION: $NC_VERSION" + + if [ -z "$NC_VERSION" ]; then + echo "NC_VERSION is empty" + exit 1 + fi + + echo "NC_VERSION=$NC_VERSION" >> $GITHUB_OUTPUT + + - name: Upload artifact result for job build-artifact + uses: actions/upload-artifact@v4 + with: + retention-days: 30 + name: nextcloud_workspace_build_artifact + path: ${{ env.TARGET_PACKAGE_NAME }} + + - name: Show changes on failure + if: failure() + run: | + git status + git --no-pager diff + exit 1 # make it red to grab attention + + upload-to-artifactory: + runs-on: self-hosted + # Upload the artifact to the Artifactory repository on PR *OR* on "ionos-dev|ionos-stable" branch push defined in the on:push:branches + if: github.event_name == 'pull_request' || github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' + + name: Push to artifactory + needs: [prepare-matrix, build-external-apps, build-artifact] + + outputs: + ARTIFACTORY_LAST_BUILD_PATH: ${{ steps.artifactory_upload.outputs.ARTIFACTORY_LAST_BUILD_PATH }} + + env: + BUILD_NAME: "nextcloud-workspace-snapshot" + + steps: + - name: Check prerequisites + run: | + # count the number of secrets that are set + echo "Checking if required secrets are set..." + error_count=0 + + if [ -z "${{ secrets.JF_ARTIFACTORY_URL }}" ]; then + # output error to github actions log + echo "::error::JF_ARTIFACTORY_URL secret is not set" + error_count=$((error_count + 1)) + fi + + if [ -z "${{ secrets.JF_ARTIFACTORY_USER }}" ]; then + echo "::error::JF_ARTIFACTORY_USER secret is not set" + error_count=$((error_count + 1)) + fi + + if [ -z "${{ secrets.JF_ACCESS_TOKEN }}" ]; then + echo "::error::JF_ACCESS_TOKEN secret is not set" + error_count=$((error_count + 1)) + fi + + # abort if any of the required secrets are not set + if [ $error_count -ne 0 ]; then + echo "::error::Required secrets are not set. Aborting." + exit 1 + fi + + - name: Download artifact zip + uses: actions/download-artifact@v5 + with: + name: nextcloud_workspace_build_artifact + + # This action sets up the JFrog CLI with the Artifactory URL and access token + - uses: jfrog/setup-jfrog-cli@7c95feb32008765e1b4e626b078dfd897c4340ad # v4.4.1 + env: + JF_URL: ${{ secrets.JF_ARTIFACTORY_URL }} + JF_USER: ${{ secrets.JF_ARTIFACTORY_USER }} + JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }} + + - name: Ping the JF server + run: | + # Ping the server + jf rt ping + + - name: Upload build to artifactory + id: artifactory_upload + run: | + # PR builds are stored in a separate directory as "dev/pr/nextcloud-workspace-pr-.zip" + # Push to "ionos-dev" branch is stored as "dev/nextcloud-workspace-.zip" + + ARTIFACTORY_STAGE_PREFIX="dev" + + # set ARTIFACTORY_STAGE_PREFIX=stable on ionos-stable branch + if [ "${{ github.ref_name }}" == "ionos-stable" ]; then + ARTIFACTORY_STAGE_PREFIX="stable" + fi + + export PATH_TO_DIRECTORY="${{ env.ARTIFACTORY_REPOSITORY_SNAPSHOT }}/${ARTIFACTORY_STAGE_PREFIX}" + PATH_TO_FILE="pr/nextcloud-workspace-pr-${{ github.event.pull_request.number }}.zip" + + if [ -z "${{ github.event.pull_request.number }}" ]; then + PATH_TO_FILE="nextcloud-workspace-${{ needs.build-artifact.outputs.NC_VERSION }}.zip" + fi + + export PATH_TO_LATEST_ARTIFACT="${PATH_TO_DIRECTORY}/${PATH_TO_FILE}" + + # Promote current build to the "latest" dev build + jf rt upload "${{ env.TARGET_PACKAGE_NAME }}" \ + --build-name "${{ env.BUILD_NAME }}" \ + --build-number ${{ github.run_number }} \ + --target-props "build.nc_version=${{ needs.build-artifact.outputs.NC_VERSION }};vcs.branch=${{ github.ref }};vcs.revision=${{ github.sha }}" \ + $PATH_TO_LATEST_ARTIFACT + + echo "ARTIFACTORY_LAST_BUILD_PATH=${PATH_TO_LATEST_ARTIFACT}" >> $GITHUB_OUTPUT + + - name: Show changes on failure + if: failure() + run: | + git status + git --no-pager diff + exit 1 # make it red to grab attention + + nextcloud-workspace-artifact-to-ghcr_io: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + name: Push artifact to ghcr.io + needs: [prepare-matrix, build-external-apps, build-artifact] + + steps: + - name: Download artifact zip + uses: actions/download-artifact@v5 + with: + name: nextcloud_workspace_build_artifact + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + + - name: Create Dockerfile + run: | + cat >Dockerfile << EOF + FROM busybox as builder + COPY ./${{ env.TARGET_PACKAGE_NAME }} / + WORKDIR /builder + RUN unzip /${{ env.TARGET_PACKAGE_NAME }} -d /builder + + FROM scratch + WORKDIR /app + VOLUME /app + COPY --from=builder /builder /app + EOF + + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Show changes on failure + if: failure() + run: | + echo "Git status:" + git status + echo "Git diff:" + git diff + exit 1 # make it red to grab attention + + trigger-remote-dev-worflow: + runs-on: self-hosted + + name: Trigger remote workflow + needs: [ build-artifact, upload-to-artifactory ] + # Trigger remote build on "ionos-dev|ionos-stable" branch *push* defined in the on:push:branches + if: github.event_name == 'push' && ( github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' ) + steps: + - name: Trigger remote workflow + run: | + # Enable command echo for debugging purposes + set -x + + # Determine build type based on branch: + # - 'ionos-dev' branch triggers 'dev' build type + # - 'ionos-stable' branch triggers 'stable' build type + BUILD_TYPE="dev" + + # Override build type for stable branch + if [ "${{ github.ref_name }}" == "ionos-stable" ]; then + BUILD_TYPE="stable" + fi + + # Trigger GitLab pipeline via webhook with build artifacts and metadata + # Passes GitHub context variables to remote GitLab workflow + curl \ + --silent \ + --insecure \ + --request POST \ + --fail-with-body \ + -o response.json \ + --form token=${{ secrets.GITLAB_TOKEN }} \ + --form ref="stable" \ + --form "variables[GITHUB_SHA]=${{ github.sha }}" \ + --form "variables[ARTIFACTORY_LAST_BUILD_PATH]=${{ needs.upload-to-artifactory.outputs.ARTIFACTORY_LAST_BUILD_PATH }}" \ + --form "variables[NC_VERSION]=${{ needs.build-artifact.outputs.NC_VERSION }}" \ + --form "variables[BUILD_ID]=${{ github.run_id }}" \ + --form "variables[BUILD_TYPE]=${BUILD_TYPE}" \ + "${{ secrets.GITLAB_TRIGGER_URL }}" || ( RETCODE="$?"; jq . response.json; exit "$RETCODE" ) + + # Disable command echo + set +x + + # Print and parse json + # jq . response.json + echo "json<> $GITHUB_OUTPUT + cat response.json >> $GITHUB_OUTPUT + echo "END" >> $GITHUB_OUTPUT + echo "web_url<> $GITHUB_OUTPUT + cat response.json | jq --raw-output '.web_url' >> $GITHUB_OUTPUT + echo "END" >> $GITHUB_OUTPUT + + - name: Show changes on failure + if: failure() + run: | + git status + git --no-pager diff + exit 1 # make it red to grab attention From 65bb3ed89e88effefa13f16b94c46d6552d51fc9 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 12 Nov 2025 12:14:41 +0100 Subject: [PATCH 02/12] IONOS(build): update artifact filename to include '-original' suffix in order not to overwrite optimized artifact Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact-original.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-artifact-original.yml b/.github/workflows/build-artifact-original.yml index a2cbfdb849649..8d76945d5b7d4 100644 --- a/.github/workflows/build-artifact-original.yml +++ b/.github/workflows/build-artifact-original.yml @@ -780,10 +780,10 @@ jobs: fi export PATH_TO_DIRECTORY="${{ env.ARTIFACTORY_REPOSITORY_SNAPSHOT }}/${ARTIFACTORY_STAGE_PREFIX}" - PATH_TO_FILE="pr/nextcloud-workspace-pr-${{ github.event.pull_request.number }}.zip" + PATH_TO_FILE="pr/nextcloud-workspace-pr-${{ github.event.pull_request.number }}-original.zip" if [ -z "${{ github.event.pull_request.number }}" ]; then - PATH_TO_FILE="nextcloud-workspace-${{ needs.build-artifact.outputs.NC_VERSION }}.zip" + PATH_TO_FILE="nextcloud-workspace-${{ needs.build-artifact.outputs.NC_VERSION }}-original.zip" fi export PATH_TO_LATEST_ARTIFACT="${PATH_TO_DIRECTORY}/${PATH_TO_FILE}" From 3d5e5c1e2a361a695ccaab852e275b7b8808a7d3 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 12 Nov 2025 15:55:13 +0100 Subject: [PATCH 03/12] IONOS(build): update fetch-depth in build-artifact.yml to integer format Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index a2cbfdb849649..c2d1e43de61a6 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -54,7 +54,7 @@ jobs: uses: actions/checkout@v5 with: submodules: true - fetch-depth: '1' + fetch-depth: 1 # Shallow clone - only need current submodule SHAs for cache detection - name: Install dependencies run: sudo apt-get update && sudo apt-get install -y make jq @@ -530,7 +530,7 @@ jobs: uses: actions/checkout@v5 with: submodules: true - fetch-depth: '1' + fetch-depth: 1 - name: Set up node with version from package.json's engines if: matrix.app.has_npm @@ -586,7 +586,7 @@ jobs: uses: actions/checkout@v5 with: submodules: true - fetch-depth: '1' + fetch-depth: 1 - name: Download build external apps uses: actions/download-artifact@v5 From 9d9c4e6f6d0c9ed748b3a8797b20f26e67de5fcb Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 12 Nov 2025 16:01:31 +0100 Subject: [PATCH 04/12] IONOS(build): update action versions for improved functionality and compatibility Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index c2d1e43de61a6..5c3ae2a7995a7 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -534,7 +534,7 @@ jobs: - name: Set up node with version from package.json's engines if: matrix.app.has_npm - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: "package.json" cache: 'npm' @@ -562,7 +562,7 @@ jobs: run: make -f IONOS/Makefile ${{ matrix.app.makefile_target }} - name: Upload ${{ matrix.app.name }} build artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: retention-days: 1 name: external-app-build-${{ matrix.app.name }} @@ -589,7 +589,7 @@ jobs: fetch-depth: 1 - name: Download build external apps - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: pattern: external-app-build-* path: apps-external/ @@ -639,7 +639,7 @@ jobs: done - name: Set up node with version from package.json's engines - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: "package.json" cache: 'npm' @@ -693,7 +693,7 @@ jobs: echo "NC_VERSION=$NC_VERSION" >> $GITHUB_OUTPUT - name: Upload artifact result for job build-artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: retention-days: 30 name: nextcloud_workspace_build_artifact @@ -750,7 +750,7 @@ jobs: fi - name: Download artifact zip - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: nextcloud_workspace_build_artifact @@ -816,7 +816,7 @@ jobs: steps: - name: Download artifact zip - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: nextcloud_workspace_build_artifact From 90ad53dec4d31ec19a14a1e5df1ba8846e50bc8e Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 12 Nov 2025 16:12:06 +0100 Subject: [PATCH 05/12] IONOS(build): add retry logic for GitLab pipeline trigger Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact.yml | 65 +++++++++++++++++++++------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index 5c3ae2a7995a7..e5735f20c58e5 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -887,22 +887,55 @@ jobs: BUILD_TYPE="stable" fi - # Trigger GitLab pipeline via webhook with build artifacts and metadata - # Passes GitHub context variables to remote GitLab workflow - curl \ - --silent \ - --insecure \ - --request POST \ - --fail-with-body \ - -o response.json \ - --form token=${{ secrets.GITLAB_TOKEN }} \ - --form ref="stable" \ - --form "variables[GITHUB_SHA]=${{ github.sha }}" \ - --form "variables[ARTIFACTORY_LAST_BUILD_PATH]=${{ needs.upload-to-artifactory.outputs.ARTIFACTORY_LAST_BUILD_PATH }}" \ - --form "variables[NC_VERSION]=${{ needs.build-artifact.outputs.NC_VERSION }}" \ - --form "variables[BUILD_ID]=${{ github.run_id }}" \ - --form "variables[BUILD_TYPE]=${BUILD_TYPE}" \ - "${{ secrets.GITLAB_TRIGGER_URL }}" || ( RETCODE="$?"; jq . response.json; exit "$RETCODE" ) + # Trigger GitLab pipeline via webhook with retry logic (3 attempts with 30s delay) + MAX_ATTEMPTS=3 + ATTEMPT=1 + TRIGGER_SUCCESS=false + DELAY_SEC=5 + + while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do + echo "Trigger attempt $ATTEMPT of $MAX_ATTEMPTS..." + + if curl \ + --silent \ + --insecure \ + --request POST \ + --fail-with-body \ + -o response.json \ + --form token=${{ secrets.GITLAB_TOKEN }} \ + --form ref="stable" \ + --form "variables[GITHUB_SHA]=${{ github.sha }}" \ + --form "variables[ARTIFACTORY_LAST_BUILD_PATH]=${{ needs.upload-to-artifactory.outputs.ARTIFACTORY_LAST_BUILD_PATH }}" \ + --form "variables[NC_VERSION]=${{ needs.build-artifact.outputs.NC_VERSION }}" \ + --form "variables[BUILD_ID]=${{ github.run_id }}" \ + --form "variables[BUILD_TYPE]=${BUILD_TYPE}" \ + "${{ secrets.GITLAB_TRIGGER_URL }}"; then + TRIGGER_SUCCESS=true + echo "✅ Trigger successful on attempt $ATTEMPT" + break + else + RETCODE="$?" + echo "⚠️ Trigger attempt $ATTEMPT failed with code $RETCODE" + if [ -f response.json ]; then + jq . response.json || cat response.json + fi + if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then + echo "Waiting ${DELAY_SEC} seconds before retry..." + sleep $DELAY_SEC + DELAY_SEC=$((DELAY_SEC * 2)) # Exponential backoff: 5s, 10s, 20s + fi + fi + + ATTEMPT=$((ATTEMPT + 1)) + done + + if [ "$TRIGGER_SUCCESS" != "true" ]; then + echo "❌ Trigger failed after $MAX_ATTEMPTS attempts" + if [ -f response.json ]; then + jq . response.json || cat response.json + fi + exit 1 + fi # Disable command echo set +x From 3d0849ef5d985ad52041515a70f11e6c6b043408 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 12 Nov 2025 16:12:38 +0100 Subject: [PATCH 06/12] IONOS(build): add retry logic for artifact upload in build-artifact.yml Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact.yml | 39 +++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index e5735f20c58e5..e603a517e1b7a 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -788,12 +788,39 @@ jobs: export PATH_TO_LATEST_ARTIFACT="${PATH_TO_DIRECTORY}/${PATH_TO_FILE}" - # Promote current build to the "latest" dev build - jf rt upload "${{ env.TARGET_PACKAGE_NAME }}" \ - --build-name "${{ env.BUILD_NAME }}" \ - --build-number ${{ github.run_number }} \ - --target-props "build.nc_version=${{ needs.build-artifact.outputs.NC_VERSION }};vcs.branch=${{ github.ref }};vcs.revision=${{ github.sha }}" \ - $PATH_TO_LATEST_ARTIFACT + # Upload with retry logic (3 attempts with 30s delay) + MAX_ATTEMPTS=3 + ATTEMPT=1 + UPLOAD_SUCCESS=false + DELAY_SEC=10 + + while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do + echo "Upload attempt $ATTEMPT of $MAX_ATTEMPTS..." + + if jf rt upload "${{ env.TARGET_PACKAGE_NAME }}" \ + --build-name "${{ env.BUILD_NAME }}" \ + --build-number ${{ github.run_number }} \ + --target-props "build.nc_version=${{ needs.build-artifact.outputs.NC_VERSION }};vcs.branch=${{ github.ref }};vcs.revision=${{ github.sha }}" \ + $PATH_TO_LATEST_ARTIFACT; then + UPLOAD_SUCCESS=true + echo "✅ Upload successful on attempt $ATTEMPT" + break + else + echo "⚠️ Upload attempt $ATTEMPT failed" + if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then + echo "Waiting $DELAY_SEC seconds before retry..." + sleep $DELAY_SEC + DELAY_SEC=$((DELAY_SEC * 2)) # Exponential backoff: delays are 10s, then 20s (sleep occurs after failed attempts) + fi + fi + + ATTEMPT=$((ATTEMPT + 1)) + done + + if [ "$UPLOAD_SUCCESS" != "true" ]; then + echo "❌ Upload failed after $MAX_ATTEMPTS attempts" + exit 1 + fi echo "ARTIFACTORY_LAST_BUILD_PATH=${PATH_TO_LATEST_ARTIFACT}" >> $GITHUB_OUTPUT From 095b5bfe89d60f0072e73d40ab79dd40443982fc Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 12 Nov 2025 16:29:14 +0100 Subject: [PATCH 07/12] IONOS(build): fix step ID naming in build-artifact.yml for consistency lets use underscore Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index e603a517e1b7a..c17c69dd12e01 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -48,7 +48,7 @@ jobs: prepare-matrix: runs-on: ubuntu-latest outputs: - external-apps-matrix: ${{ steps.set-matrix.outputs.matrix }} + external-apps-matrix: ${{ steps.set_matrix.outputs.matrix }} steps: - name: Checkout repository uses: actions/checkout@v5 @@ -60,7 +60,7 @@ jobs: run: sudo apt-get update && sudo apt-get install -y make jq - name: Set matrix - id: set-matrix + id: set_matrix run: | # Create matrix configuration as a compact JSON string matrix='[ @@ -338,7 +338,7 @@ jobs: # So we need to extract just the JSON part generated_matrix=$(echo "$makefile_output" | grep -v '^\[i\]' || echo "$makefile_output") - workflow_matrix='${{ steps.set-matrix.outputs.matrix }}' + workflow_matrix='${{ steps.set_matrix.outputs.matrix }}' # Debug output echo "Generated matrix length: ${#generated_matrix}" @@ -487,7 +487,7 @@ jobs: echo "make -f IONOS/Makefile generate_external_apps_matrix_json" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "Then update the \`matrix\` variable in \`.github/workflows/build-artifact.yml\` in the set-matrix step with the generated output." >> $GITHUB_STEP_SUMMARY + echo "Then update the \`matrix\` variable in \`.github/workflows/build-artifact.yml\` in the set_matrix step with the generated output." >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Show full diff in expandable section From 0616c803504c8d62540c1716ae8424c7a12cde55 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 12 Nov 2025 16:33:27 +0100 Subject: [PATCH 08/12] IONOS(build): generate apps matrix dynamically from Makefile drop validation since it is now Redundant (by design) Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact.yml | 459 +-------------------------- 1 file changed, 16 insertions(+), 443 deletions(-) diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index c17c69dd12e01..4900aa4329ba2 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -59,458 +59,31 @@ jobs: - name: Install dependencies run: sudo apt-get update && sudo apt-get install -y make jq - - name: Set matrix + - name: Generate apps matrix dynamically from Makefile id: set_matrix run: | - # Create matrix configuration as a compact JSON string - matrix='[ - { - "name": "activity", - "path": "apps-external/activity", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_activity_app" - }, - { - "name": "assistant", - "path": "apps-external/assistant", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_assistant_app" - }, - { - "name": "calendar", - "path": "apps-external/calendar", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_calendar_app" - }, - { - "name": "circles", - "path": "apps-external/circles", - "has_npm": false, - "has_composer": true, - "makefile_target": "build_circles_app" - }, - { - "name": "collectives", - "path": "apps-external/collectives", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_collectives_app" - }, - { - "name": "contacts", - "path": "apps-external/contacts", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_contacts_app" - }, - { - "name": "deck", - "path": "apps-external/deck", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_deck_app" - }, - { - "name": "end_to_end_encryption", - "path": "apps-external/end_to_end_encryption", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_end_to_end_encryption_app" - }, - { - "name": "forms", - "path": "apps-external/forms", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_forms_app" - }, - { - "name": "groupfolders", - "path": "apps-external/groupfolders", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_groupfolders_app" - }, - { - "name": "integration_openai", - "path": "apps-external/integration_openai", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_integration_openai_app" - }, - { - "name": "mail", - "path": "apps-external/mail", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_mail_app" - }, - { - "name": "ncw_apps_menu", - "path": "apps-external/ncw_apps_menu", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_ncw_apps_menu_app" - }, - { - "name": "ncw_mailtemplate", - "path": "apps-external/ncw_mailtemplate", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_ncw_mailtemplate_app" - }, - { - "name": "notes", - "path": "apps-external/notes", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_notes_app" - }, - { - "name": "notifications", - "path": "apps-external/notifications", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_notifications_app" - }, - { - "name": "notify_push", - "path": "apps-external/notify_push", - "has_npm": false, - "has_composer": true, - "makefile_target": "build_notify_push_app" - }, - { - "name": "password_policy", - "path": "apps-external/password_policy", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_password_policy_app" - }, - { - "name": "richdocuments", - "path": "apps-external/richdocuments", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_richdocuments_app" - }, - { - "name": "spreed", - "path": "apps-external/spreed", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_spreed_app" - }, - { - "name": "tables", - "path": "apps-external/tables", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_tables_app" - }, - { - "name": "tasks", - "path": "apps-external/tasks", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_tasks_app" - }, - { - "name": "text", - "path": "apps-external/text", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_text_app" - }, - { - "name": "twofactor_totp", - "path": "apps-external/twofactor_totp", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_twofactor_totp_app" - }, - { - "name": "user_oidc", - "path": "apps-external/user_oidc", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_user_oidc_app" - }, - { - "name": "viewer", - "path": "apps-external/viewer", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_viewer_app" - }, - { - "name": "whiteboard", - "path": "apps-external/whiteboard", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_whiteboard_app" - } - ]' - - # Validate JSON and output as compact format - if echo "$matrix" | jq empty 2>/dev/null; then - echo "matrix=$(echo "$matrix" | jq -c '.')" >> $GITHUB_OUTPUT - echo "Matrix configuration set successfully" - else - echo "Error: Invalid JSON in matrix configuration" - exit 1 - fi - - - name: Validate matrix against Makefile - run: | - set +e # Intentionally allow script to continue on error for custom error handling and reporting to GITHUB_STEP_SUMMARY - set -u # Exit on undefined variable - - echo "### 🔍 Matrix Validation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Debug: Check if apps-external exists - echo "Checking apps-external directory..." - if [ ! -d "apps-external" ]; then - echo "❌ **Error:** apps-external directory does not exist!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Directory listing:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - ls -la >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - echo "Apps-external directory exists. Listing contents:" - ls -la apps-external/ | head -10 - - # Check if jq is available - echo "Checking if jq is installed..." - if ! command -v jq &> /dev/null; then - echo "❌ **Error:** jq is not installed!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "jq is required for matrix generation but was not found in PATH." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "jq version: $(jq --version)" - + # Generate matrix from Makefile - single source of truth echo "Generating matrix from Makefile..." - # Capture both stdout and stderr separately to better diagnose issues - makefile_output=$(make -f IONOS/Makefile generate_external_apps_matrix_json 2>&1) - makefile_exit_code=$? - - echo "Makefile exit code: ${makefile_exit_code}" - echo "Makefile output length: ${#makefile_output}" - - # Debug: Check if GITHUB_STEP_SUMMARY is set - echo "GITHUB_STEP_SUMMARY: ${GITHUB_STEP_SUMMARY:-NOT SET}" - - # If the Makefile command failed, show the error - if [ ${makefile_exit_code} -ne 0 ]; then - echo "" - echo "=== MAKEFILE ERROR ===" - echo "Exit code: ${makefile_exit_code}" - echo "Output:" - echo "$makefile_output" - echo "=====================" - echo "" - - # Write to summary - echo "❌ **Error:** Makefile command failed with exit code ${makefile_exit_code}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "Makefile error output" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$makefile_output" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - - echo "Error written to summary file: ${GITHUB_STEP_SUMMARY}" - exit 1 - fi - - # Filter out the info message to get just the JSON - # The Makefile outputs "[i] Generating..." to stderr, but we captured everything with 2>&1 - # So we need to extract just the JSON part - generated_matrix=$(echo "$makefile_output" | grep -v '^\[i\]' || echo "$makefile_output") - - workflow_matrix='${{ steps.set_matrix.outputs.matrix }}' - - # Debug output - echo "Generated matrix length: ${#generated_matrix}" - echo "Workflow matrix length: ${#workflow_matrix}" + matrix_output=$(make -f IONOS/Makefile generate_external_apps_matrix_json 2>&1) - # Show first 200 chars of generated matrix for debugging - if [ -n "$generated_matrix" ]; then - echo "Generated matrix preview: ${generated_matrix:0:200}..." - fi - - # Validate that we got valid JSON - if ! echo "$generated_matrix" | jq empty 2>/dev/null; then - echo "❌ **Error:** Generated matrix is not valid JSON" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "Invalid JSON output" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$generated_matrix" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - exit 1 + # Filter out info messages to get just the JSON + # Note: Use same filtering logic as validation script to ensure consistency + if echo "$matrix_output" | grep -q '^\[i\]'; then + matrix=$(echo "$matrix_output" | grep -v '^\[i\]') + else + matrix="$matrix_output" fi - # Validate that we got data - if [ -z "$generated_matrix" ] || [ -z "$workflow_matrix" ]; then - echo "❌ **Error:** Failed to load matrices" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- Generated matrix empty: $([ -z "$generated_matrix" ] && echo "yes" || echo "no")" >> $GITHUB_STEP_SUMMARY - echo "- Workflow matrix empty: $([ -z "$workflow_matrix" ] && echo "yes" || echo "no")" >> $GITHUB_STEP_SUMMARY - echo "- Makefile exit code: ${makefile_exit_code}" >> $GITHUB_STEP_SUMMARY - + # Validate JSON + if ! echo "$matrix" | jq empty 2>/dev/null; then + echo "Error: Generated matrix is not valid JSON" + echo "Output: $matrix_output" exit 1 fi - # Sort both matrices for comparison - generated_sorted=$(echo "$generated_matrix" | jq -S '.' 2>&1 || echo "ERROR") - workflow_sorted=$(echo "$workflow_matrix" | jq -S '.' 2>&1 || echo "ERROR") - - echo "Sorted matrix lengths - generated: ${#generated_sorted}, workflow: ${#workflow_sorted}" - - # Compare the two matrices - if [ "$generated_sorted" = "$workflow_sorted" ]; then - echo "✅ **Validation passed!** The workflow matrix matches the Makefile configuration." >> $GITHUB_STEP_SUMMARY - echo "" - echo "✅ Matrix validation passed!" - else - echo "❌ **Validation failed!** The workflow matrix does not match the Makefile configuration." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - echo "Starting detailed comparison..." - - # Extract app names from both matrices - generated_apps=$(echo "$generated_matrix" | jq -r '.[].name' 2>/dev/null | sort || echo "") - workflow_apps=$(echo "$workflow_matrix" | jq -r '.[].name' 2>/dev/null | sort || echo "") - - echo "Generated apps count: $(echo "$generated_apps" | wc -l)" - echo "Workflow apps count: $(echo "$workflow_apps" | wc -l)" - - # Find missing apps (in Makefile but not in workflow) - missing_apps=$(comm -23 <(echo "$generated_apps") <(echo "$workflow_apps")) - if [ $? -ne 0 ]; then - echo "Error: comm command failed when finding missing apps." >&2 - exit 1 - fi - # Find extra apps (in workflow but not in Makefile) - extra_apps=$(comm -13 <(echo "$generated_apps") <(echo "$workflow_apps")) - if [ $? -ne 0 ]; then - echo "Error: comm command failed when finding extra apps." >&2 - exit 1 - fi - - echo "Missing apps: ${missing_apps:-none}" - echo "Extra apps: ${extra_apps:-none}" - - if [ -n "$missing_apps" ]; then - echo "#### ⚠️ Missing Apps" >> $GITHUB_STEP_SUMMARY - echo "The following apps are configured in the Makefile but missing from the workflow:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$missing_apps" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi - - if [ -n "$extra_apps" ]; then - echo "#### ⚠️ Extra Apps" >> $GITHUB_STEP_SUMMARY - echo "The following apps are in the workflow but not configured in the Makefile:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$extra_apps" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi - - # Check for configuration mismatches in common apps - common_apps=$(comm -12 <(echo "$generated_apps") <(echo "$workflow_apps") 2>/dev/null || echo "") - - echo "Common apps count: $(echo "$common_apps" | wc -l)" - - if [ -n "$common_apps" ]; then - mismatched_apps="" - - while IFS= read -r app; do - [ -z "$app" ] && continue - gen_config=$(echo "$generated_matrix" | jq -c --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "") - wf_config=$(echo "$workflow_matrix" | jq -c --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "") - - if [ -n "$gen_config" ] && [ -n "$wf_config" ] && [ "$gen_config" != "$wf_config" ]; then - mismatched_apps="${mismatched_apps}${app}"$'\n' - fi - done <<< "$common_apps" - - echo "Mismatched apps: ${mismatched_apps:-none}" - - if [ -n "$mismatched_apps" ]; then - echo "#### ⚠️ Configuration Mismatches" >> $GITHUB_STEP_SUMMARY - echo "The following apps have different configurations (has_npm, has_composer, etc.):" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$mismatched_apps" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "📋 Detailed differences" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```diff' >> $GITHUB_STEP_SUMMARY - - while IFS= read -r app; do - [ -z "$app" ] && continue - echo "=== $app ===" >> $GITHUB_STEP_SUMMARY - diff -u --label "Workflow" --label "Makefile" \ - <(echo "$workflow_matrix" | jq --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "{}") \ - <(echo "$generated_matrix" | jq --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "{}") \ - >> $GITHUB_STEP_SUMMARY 2>&1 || true - done <<< "$mismatched_apps" - - echo '```' >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi - fi - - # Provide fix instructions - echo "#### 🔧 How to Fix" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Run this command locally to generate the correct matrix:" >> $GITHUB_STEP_SUMMARY - echo '```bash' >> $GITHUB_STEP_SUMMARY - echo "make -f IONOS/Makefile generate_external_apps_matrix_json" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Then update the \`matrix\` variable in \`.github/workflows/build-artifact.yml\` in the set_matrix step with the generated output." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Show full diff in expandable section - echo "
" >> $GITHUB_STEP_SUMMARY - echo "📄 Full matrix comparison" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Workflow Matrix:**" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - echo "$workflow_matrix" | jq '.' 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "$workflow_matrix" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Makefile Matrix:**" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - echo "$generated_matrix" | jq '.' 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "$generated_matrix" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - - echo "" - echo "❌ ERROR: Matrix validation failed!" - echo "See the job summary for details on what's wrong and how to fix it." - echo "Summary file size: $(wc -c < $GITHUB_STEP_SUMMARY || echo 0) bytes" - exit 1 - fi + # Output as compact format + echo "matrix=$(echo "$matrix" | jq -c '.')" >> $GITHUB_OUTPUT + echo "Matrix generated successfully with $(echo "$matrix" | jq 'length') apps" build-external-apps: runs-on: ubuntu-latest From 813754b4ab09b2722c464deff3e4940f2796bc8a Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 11 Nov 2025 15:33:09 +0100 Subject: [PATCH 09/12] IONOS(build): optimize build workflow - only rebuild changed apps Optimized build workflow that uses cache-based detection - Checks cache for each app's current SHA - Only builds apps with no cached build - Significantly reduces build time through smart caching Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact.yml | 327 +++++++++++++++++++++++++-- 1 file changed, 304 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index 4900aa4329ba2..ff9a48391a938 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -1,12 +1,13 @@ -name: Build Nextcloud Workspace artifact +name: Build Nextcloud Workspace artifact (Optimized) # SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors # SPDX-FileCopyrightText: 2025 STRATO AG # SPDX-License-Identifier: AGPL-3.0-or-later -# The Nextcloud Workspace source is packaged as a container image. -# This is a workaround because releases cannot be created without tags, -# and we want to be able to create snapshots from branches. +# Optimized build workflow using GitHub Actions cache +# - Checks GitHub Actions cache for each app's current SHA +# - Only builds apps without cached artifacts +# - Significantly reduces build time by reusing cached builds via GitHub Actions cache on: pull_request: @@ -40,6 +41,10 @@ env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} ARTIFACTORY_REPOSITORY_SNAPSHOT: ionos-productivity-ncwserver-snapshot + # Cache version - increment this to invalidate all caches when build process changes + # Update when: Node.js version changes, PHP version changes, build scripts modified, etc. + # Format: v. (e.g., v1.0, v1.1, v2.0) + CACHE_VERSION: v1.0 permissions: contents: read @@ -48,7 +53,11 @@ jobs: prepare-matrix: runs-on: ubuntu-latest outputs: - external-apps-matrix: ${{ steps.set_matrix.outputs.matrix }} + apps_to_build: ${{ steps.detect.outputs.apps_to_build }} + apps_to_restore: ${{ steps.detect.outputs.apps_to_restore }} + external_apps_matrix: ${{ steps.set_matrix.outputs.matrix }} + has_cached_apps: ${{ steps.detect.outputs.has_cached_apps }} + apps_sha_map: ${{ steps.detect.outputs.apps_sha_map }} steps: - name: Checkout repository uses: actions/checkout@v5 @@ -85,9 +94,165 @@ jobs: echo "matrix=$(echo "$matrix" | jq -c '.')" >> $GITHUB_OUTPUT echo "Matrix generated successfully with $(echo "$matrix" | jq 'length') apps" + - name: Collect apps and their SHAs for cache-based building + id: detect + env: + GH_TOKEN: ${{ github.token }} + CACHE_VERSION: ${{ env.CACHE_VERSION }} + run: | + set -e # Exit on error + set -u # Exit on undefined variable + set -o pipefail # Exit if any command in pipeline fails + + echo "Collecting app SHAs and checking cache status..." + echo "" + + # Get the matrix from previous step + MATRIX='${{ steps.set_matrix.outputs.matrix }}' + + # Build JSON array for apps that actually need building + APPS_TO_BUILD="[]" + # Build JSON array for apps that are cached and need restoring + APPS_TO_RESTORE="[]" + APPS_CHECKED=0 + APPS_CACHED=0 + APPS_TO_BUILD_COUNT=0 + APPS_SHA_MAP="{}" + + echo "### 📦 Cache Status Report" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| App | SHA | Cache Key | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-----|-----|-----------|--------|" >> $GITHUB_STEP_SUMMARY + + # Iterate through each app in the matrix + while IFS= read -r app_json; do + APP_NAME=$(echo "$app_json" | jq -r '.name') + APP_PATH=$(echo "$app_json" | jq -r '.path') + + APPS_CHECKED=$((APPS_CHECKED + 1)) + + # Get current submodule SHA + if [ -d "$APP_PATH" ]; then + CURRENT_SHA=$(git -C "$APP_PATH" rev-parse HEAD 2>/dev/null || echo "") + else + echo "⊘ $APP_NAME - directory not found, will build" + echo "| $APP_NAME | N/A | N/A | ⊘ Directory not found |" >> $GITHUB_STEP_SUMMARY + APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c --arg app "$APP_NAME" --arg sha "unknown" '. + [{name: $app, sha: $sha}]') + APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1)) + continue + fi + + if [ -z "$CURRENT_SHA" ]; then + echo "⊘ $APP_NAME - not a git repo, will build" + echo "| $APP_NAME | N/A | N/A | ⊘ Not a git repo |" >> $GITHUB_STEP_SUMMARY + APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c --arg app "$APP_NAME" --arg sha "unknown" '. + [{name: $app, sha: $sha}]') + APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1)) + continue + fi + + # Add SHA to the map for all apps (regardless of cache status) + APPS_SHA_MAP=$(echo "$APPS_SHA_MAP" | jq -c --arg app "$APP_NAME" --arg sha "$CURRENT_SHA" '.[$app] = $sha') + + # Cache key that would be used for this app + # Format: -app-build-- + CACHE_KEY="${CACHE_VERSION}-app-build-${APP_NAME}-${CURRENT_SHA}" + SHORT_SHA="${CURRENT_SHA:0:8}" + + echo -n " Checking $APP_NAME (SHA: $SHORT_SHA)... " + + # Check if cache exists using GitHub CLI + CACHE_EXISTS="false" + if ! CACHE_LIST=$(gh cache list --key "$CACHE_KEY" --json key --jq ".[].key" 2>&1); then + echo "⚠️ Warning: Failed to query cache for $APP_NAME: $CACHE_LIST" + echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | ⚠️ Cache check failed - will build |" >> $GITHUB_STEP_SUMMARY + APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c --arg app "$APP_NAME" --arg sha "$CURRENT_SHA" '. + [{name: $app, sha: $sha}]') + APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1)) + continue + fi + if echo "$CACHE_LIST" | grep -q "^${CACHE_KEY}$"; then + CACHE_EXISTS="true" + APPS_CACHED=$((APPS_CACHED + 1)) + echo "✓ cached" + echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | ✅ Cached |" >> $GITHUB_STEP_SUMMARY + # Add to restore list with SHA + APPS_TO_RESTORE=$(echo "$APPS_TO_RESTORE" | jq -c --argjson app "$app_json" --arg sha "$CURRENT_SHA" '. + [($app + {sha: $sha})]') + else + echo "⚡ needs build" + echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | 🔨 Needs build |" >> $GITHUB_STEP_SUMMARY + APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c --arg app "$APP_NAME" --arg sha "$CURRENT_SHA" '. + [{name: $app, sha: $sha}]') + APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1)) + fi + + done < <(echo "$MATRIX" | jq -c '.[]') + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Summary:**" >> $GITHUB_STEP_SUMMARY + echo "- Total apps checked: $APPS_CHECKED" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Apps with cached builds: $APPS_CACHED" >> $GITHUB_STEP_SUMMARY + echo "- 🔨 Apps needing build: $APPS_TO_BUILD_COUNT" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ $APPS_CACHED -gt 0 ] && [ $APPS_CHECKED -gt 0 ]; then + CACHE_HIT_PERCENT=$((APPS_CACHED * 100 / APPS_CHECKED)) + echo "**Cache hit rate: ${CACHE_HIT_PERCENT}%** 🎯" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + echo "" + echo "Summary:" + echo " Total apps: $APPS_CHECKED" + echo " Cached: $APPS_CACHED" + echo " To build: $APPS_TO_BUILD_COUNT" + + # Validate that we built valid JSON + if ! echo "$APPS_TO_BUILD" | jq empty 2>/dev/null; then + echo "ERROR: Failed to build valid JSON for apps_to_build" + echo "Content: $APPS_TO_BUILD" + exit 1 + fi + + if ! echo "$APPS_TO_RESTORE" | jq empty 2>/dev/null; then + echo "ERROR: Failed to build valid JSON for apps_to_restore" + echo "Content: $APPS_TO_RESTORE" + exit 1 + fi + + # Output app list with SHAs for the build job to use + # Use proper multiline output format for GitHub Actions + echo "apps_to_build<> $GITHUB_OUTPUT + echo "$APPS_TO_BUILD" >> $GITHUB_OUTPUT + echo "APPS_TO_BUILD_JSON_EOF" >> $GITHUB_OUTPUT + + # Output the list of apps to restore from cache + echo "apps_to_restore<> $GITHUB_OUTPUT + echo "$APPS_TO_RESTORE" >> $GITHUB_OUTPUT + echo "APPS_TO_RESTORE_JSON_EOF" >> $GITHUB_OUTPUT + + # Output the SHA map for all apps + echo "apps_sha_map<> $GITHUB_OUTPUT + echo "$APPS_SHA_MAP" >> $GITHUB_OUTPUT + echo "APPS_SHA_MAP_JSON_EOF" >> $GITHUB_OUTPUT + + # Determine if there are cached apps by comparing counts + # If apps_to_build count is less than total apps, some are cached + if [ $APPS_TO_BUILD_COUNT -lt $APPS_CHECKED ]; then + echo "has_cached_apps=true" >> $GITHUB_OUTPUT + else + echo "has_cached_apps=false" >> $GITHUB_OUTPUT + fi + + echo "" + if [ $APPS_TO_BUILD_COUNT -eq 0 ]; then + echo "🎉 All apps are cached! No builds needed." + else + echo "✓ Will build $APPS_TO_BUILD_COUNT app(s)" + fi + build-external-apps: runs-on: ubuntu-latest needs: prepare-matrix + # Only run if there are apps to build + if: needs.prepare-matrix.outputs.apps_to_build != '[]' permissions: contents: read @@ -96,9 +261,38 @@ jobs: strategy: max-parallel: 20 matrix: - app: ${{ fromJson(needs.prepare-matrix.outputs.external-apps-matrix) }} + # Use the filtered list of apps that need building (not in cache) + app_info: ${{ fromJson(needs.prepare-matrix.outputs.apps_to_build) }} steps: + - name: Get app configuration from full matrix + id: app-config + run: | + # Get the full matrix to look up app configuration + FULL_MATRIX='${{ needs.prepare-matrix.outputs.external_apps_matrix }}' + APP_NAME='${{ matrix.app_info.name }}' + + # Find the app configuration in the full matrix + APP_CONFIG=$(echo "$FULL_MATRIX" | jq -c --arg name "$APP_NAME" '.[] | select(.name == $name)') + + if [ -z "$APP_CONFIG" ]; then + echo "ERROR: Could not find configuration for $APP_NAME" + exit 1 + fi + + # Extract configuration values + APP_PATH=$(echo "$APP_CONFIG" | jq -r '.path') + HAS_NPM=$(echo "$APP_CONFIG" | jq -r '.has_npm') + HAS_COMPOSER=$(echo "$APP_CONFIG" | jq -r '.has_composer') + MAKEFILE_TARGET=$(echo "$APP_CONFIG" | jq -r '.makefile_target') + + # Set outputs + echo "path=$APP_PATH" >> $GITHUB_OUTPUT + echo "has-npm=$HAS_NPM" >> $GITHUB_OUTPUT + echo "has-composer=$HAS_COMPOSER" >> $GITHUB_OUTPUT + echo "makefile-target=$MAKEFILE_TARGET" >> $GITHUB_OUTPUT + echo "Building $APP_NAME from $APP_PATH (SHA: ${{ matrix.app_info.sha }})" + - name: Checkout server uses: actions/checkout@v5 with: @@ -106,15 +300,15 @@ jobs: fetch-depth: 1 - name: Set up node with version from package.json's engines - if: matrix.app.has_npm + if: steps.app-config.outputs.has-npm == 'true' uses: actions/setup-node@v6 with: node-version-file: "package.json" cache: 'npm' - cache-dependency-path: ${{ matrix.app.path }}/package-lock.json + cache-dependency-path: ${{ steps.app-config.outputs.path }}/package-lock.json - name: Setup PHP with PECL extension - if: matrix.app.has_composer + if: steps.app-config.outputs.has-composer == 'true' uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 #v2.31.1 with: tools: composer:v2 @@ -122,19 +316,86 @@ jobs: env: runner: ubuntu-latest - - name: Cache Composer dependencies for ${{ matrix.app.name }} - if: matrix.app.has_composer + - name: Cache Composer dependencies for ${{ matrix.app_info.name }} + if: steps.app-config.outputs.has-composer == 'true' uses: actions/cache@v4 with: - path: ${{ matrix.app.path }}/vendor - key: ${{ runner.os }}-composer-${{ matrix.app.name }}-${{ hashFiles(format('{0}/composer.lock', matrix.app.path)) }} + path: ${{ steps.app-config.outputs.path }}/vendor + key: ${{ runner.os }}-composer-${{ matrix.app_info.name }}-${{ hashFiles(format('{0}/composer.lock', steps.app-config.outputs.path)) }} restore-keys: | - ${{ runner.os }}-composer-${{ matrix.app.name }}- + ${{ runner.os }}-composer-${{ matrix.app_info.name }}- + + - name: Build ${{ matrix.app_info.name }} app + run: make -f IONOS/Makefile ${{ steps.app-config.outputs.makefile-target }} + + - name: Report build completion + if: success() + run: | + echo "### ✅ Built ${{ matrix.app_info.name }}" >> $GITHUB_STEP_SUMMARY + echo "- **SHA:** \`${{ matrix.app_info.sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Path:** ${{ steps.app-config.outputs.path }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status:** Success" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Save built app to cache for future runs + - name: Save build artifacts to cache + uses: actions/cache/save@v4 + with: + path: ${{ steps.app-config.outputs.path }} + key: ${{ env.CACHE_VERSION }}-app-build-${{ matrix.app_info.name }}-${{ matrix.app_info.sha }} + + - name: Upload ${{ matrix.app_info.name }} build artifacts + uses: actions/upload-artifact@v5 + with: + retention-days: 1 + name: external-app-build-${{ matrix.app_info.name }} + path: | + ${{ steps.app-config.outputs.path }} + !${{ steps.app-config.outputs.path }}/node_modules + + restore-cached-apps: + runs-on: ubuntu-latest + needs: prepare-matrix + # Only run if there are cached apps to restore + if: needs.prepare-matrix.outputs.apps_to_restore != '[]' + + permissions: + contents: read + + name: restore-cached-apps + strategy: + max-parallel: 20 + matrix: + # Use the filtered list of apps that need restoring from cache + app: ${{ fromJson(needs.prepare-matrix.outputs.apps_to_restore) }} + + steps: + - name: Restore cached build from cache + uses: actions/cache/restore@v4 + with: + path: ${{ matrix.app.path }} + key: ${{ env.CACHE_VERSION }}-app-build-${{ matrix.app.name }}-${{ matrix.app.sha }} + fail-on-cache-miss: true + + - name: Validate cached build + run: | + APP_PATH="${{ matrix.app.path }}" + + # Check that the directory exists and is not empty + if [ ! -d "$APP_PATH" ] || [ -z "$(ls -A $APP_PATH)" ]; then + echo "❌ Cache validation failed: Directory is empty or missing" + exit 1 + fi + + # Check for appinfo/info.xml (required for all Nextcloud apps) + if [ ! -f "$APP_PATH/appinfo/info.xml" ]; then + echo "❌ Cache validation failed: Missing appinfo/info.xml" + exit 1 + fi - - name: Build ${{ matrix.app.name }} app - run: make -f IONOS/Makefile ${{ matrix.app.makefile_target }} + echo "✅ Cache validation passed for ${{ matrix.app.name }}" - - name: Upload ${{ matrix.app.name }} build artifacts + - name: Upload cached ${{ matrix.app.name }} build artifacts uses: actions/upload-artifact@v5 with: retention-days: 1 @@ -145,7 +406,12 @@ jobs: build-artifact: runs-on: ubuntu-latest - needs: [prepare-matrix, build-external-apps] + needs: [prepare-matrix, build-external-apps, restore-cached-apps] + # Always run this job, even if restore-cached-apps is skipped + if: | + always() && + (needs.build-external-apps.result == 'success' || needs.build-external-apps.result == 'skipped') && + (needs.restore-cached-apps.result == 'success' || needs.restore-cached-apps.result == 'skipped') permissions: contents: read @@ -282,10 +548,15 @@ jobs: upload-to-artifactory: runs-on: self-hosted # Upload the artifact to the Artifactory repository on PR *OR* on "ionos-dev|ionos-stable" branch push defined in the on:push:branches - if: github.event_name == 'pull_request' || github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' + if: | + always() && + (github.event_name == 'pull_request' || github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable') && + (needs.build-external-apps.result == 'success' || needs.build-external-apps.result == 'skipped') && + (needs.restore-cached-apps.result == 'success' || needs.restore-cached-apps.result == 'skipped') && + needs.build-artifact.result == 'success' name: Push to artifactory - needs: [prepare-matrix, build-external-apps, build-artifact] + needs: [prepare-matrix, build-external-apps, restore-cached-apps, build-artifact] outputs: ARTIFACTORY_LAST_BUILD_PATH: ${{ steps.artifactory_upload.outputs.ARTIFACTORY_LAST_BUILD_PATH }} @@ -406,13 +677,19 @@ jobs: nextcloud-workspace-artifact-to-ghcr_io: runs-on: ubuntu-latest + # Only run if build-artifact succeeded + if: | + always() && + (needs.build-external-apps.result == 'success' || needs.build-external-apps.result == 'skipped') && + (needs.restore-cached-apps.result == 'success' || needs.restore-cached-apps.result == 'skipped') && + needs.build-artifact.result == 'success' permissions: contents: read packages: write name: Push artifact to ghcr.io - needs: [prepare-matrix, build-external-apps, build-artifact] + needs: [prepare-matrix, build-external-apps, restore-cached-apps, build-artifact] steps: - name: Download artifact zip @@ -468,9 +745,13 @@ jobs: runs-on: self-hosted name: Trigger remote workflow - needs: [ build-artifact, upload-to-artifactory ] + needs: [build-artifact, upload-to-artifactory] # Trigger remote build on "ionos-dev|ionos-stable" branch *push* defined in the on:push:branches - if: github.event_name == 'push' && ( github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' ) + if: | + github.event_name == 'push' && + (github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable') && + needs.build-artifact.result == 'success' && + needs.upload-to-artifactory.result == 'success' steps: - name: Trigger remote workflow run: | From ca30478f0f0e96deffb087ecd68ee2ff3ece0c44 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 12 Nov 2025 19:51:54 +0100 Subject: [PATCH 10/12] IONOS(build): ensure jobs depend on successful matrix preparation Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index ff9a48391a938..e89c0fab15513 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -410,6 +410,7 @@ jobs: # Always run this job, even if restore-cached-apps is skipped if: | always() && + needs.prepare-matrix.result == 'success' && (needs.build-external-apps.result == 'success' || needs.build-external-apps.result == 'skipped') && (needs.restore-cached-apps.result == 'success' || needs.restore-cached-apps.result == 'skipped') @@ -551,6 +552,7 @@ jobs: if: | always() && (github.event_name == 'pull_request' || github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable') && + needs.prepare-matrix.result == 'success' && (needs.build-external-apps.result == 'success' || needs.build-external-apps.result == 'skipped') && (needs.restore-cached-apps.result == 'success' || needs.restore-cached-apps.result == 'skipped') && needs.build-artifact.result == 'success' @@ -680,6 +682,7 @@ jobs: # Only run if build-artifact succeeded if: | always() && + needs.prepare-matrix.result == 'success' && (needs.build-external-apps.result == 'success' || needs.build-external-apps.result == 'skipped') && (needs.restore-cached-apps.result == 'success' || needs.restore-cached-apps.result == 'skipped') && needs.build-artifact.result == 'success' @@ -745,11 +748,12 @@ jobs: runs-on: self-hosted name: Trigger remote workflow - needs: [build-artifact, upload-to-artifactory] + needs: [prepare-matrix, build-artifact, upload-to-artifactory] # Trigger remote build on "ionos-dev|ionos-stable" branch *push* defined in the on:push:branches if: | github.event_name == 'push' && (github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable') && + needs.prepare-matrix.result == 'success' && needs.build-artifact.result == 'success' && needs.upload-to-artifactory.result == 'success' steps: From 80e40e2fae6942ed461b1daf9c4e2b5db361abbf Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Thu, 13 Nov 2025 14:42:11 +0100 Subject: [PATCH 11/12] IONOS(build): add manual trigger for comparison testing in build-artifact.yml Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-artifact.yml b/.github/workflows/build-artifact.yml index e89c0fab15513..a110ac203da71 100644 --- a/.github/workflows/build-artifact.yml +++ b/.github/workflows/build-artifact.yml @@ -31,6 +31,13 @@ on: branches: - ionos-dev - ionos-stable + workflow_dispatch: # Manual trigger only for comparison testing + inputs: + force_rebuild: + description: 'Force rebuild all apps (ignore cache)' + required: false + type: boolean + default: false concurrency: group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/ionos-dev' && github.run_id || github.event.pull_request.number || github.ref }} From 57932132af6b0372c1e7db73f95d01232bdb721d Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 2 Dec 2025 14:15:54 +0100 Subject: [PATCH 12/12] refactor: remove not used build-artifact-original.yml in favor of build-artifact.yml Signed-off-by: Misha M.-Kupriyanov --- .github/workflows/build-artifact-original.yml | 924 ------------------ 1 file changed, 924 deletions(-) delete mode 100644 .github/workflows/build-artifact-original.yml diff --git a/.github/workflows/build-artifact-original.yml b/.github/workflows/build-artifact-original.yml deleted file mode 100644 index 8d76945d5b7d4..0000000000000 --- a/.github/workflows/build-artifact-original.yml +++ /dev/null @@ -1,924 +0,0 @@ -name: Build Nextcloud Workspace artifact - -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-FileCopyrightText: 2025 STRATO AG -# SPDX-License-Identifier: AGPL-3.0-or-later - -# The Nextcloud Workspace source is packaged as a container image. -# This is a workaround because releases cannot be created without tags, -# and we want to be able to create snapshots from branches. - -on: - pull_request: - paths: - - '.github/workflows/**' - - 'src/**' - - 'apps/**' - - 'apps/**/appinfo/info.xml' - - 'apps-external/**' - - 'IONOS' - - 'package.json' - - 'package-lock.json' - - 'themes/**' - - 'lib/**' - - 'tsconfig.json' - - '**.js' - - '**.ts' - - '**.vue' - - '.gitmodules' - push: - branches: - - ionos-dev - - ionos-stable - -concurrency: - group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/ionos-dev' && github.run_id || github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - TARGET_PACKAGE_NAME: nc-workspace.zip - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - ARTIFACTORY_REPOSITORY_SNAPSHOT: ionos-productivity-ncwserver-snapshot - -permissions: - contents: read - -jobs: - prepare-matrix: - runs-on: ubuntu-latest - outputs: - external-apps-matrix: ${{ steps.set-matrix.outputs.matrix }} - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - submodules: true - fetch-depth: '1' - - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y make jq - - - name: Set matrix - id: set-matrix - run: | - # Create matrix configuration as a compact JSON string - matrix='[ - { - "name": "activity", - "path": "apps-external/activity", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_activity_app" - }, - { - "name": "assistant", - "path": "apps-external/assistant", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_assistant_app" - }, - { - "name": "calendar", - "path": "apps-external/calendar", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_calendar_app" - }, - { - "name": "circles", - "path": "apps-external/circles", - "has_npm": false, - "has_composer": true, - "makefile_target": "build_circles_app" - }, - { - "name": "collectives", - "path": "apps-external/collectives", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_collectives_app" - }, - { - "name": "contacts", - "path": "apps-external/contacts", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_contacts_app" - }, - { - "name": "deck", - "path": "apps-external/deck", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_deck_app" - }, - { - "name": "end_to_end_encryption", - "path": "apps-external/end_to_end_encryption", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_end_to_end_encryption_app" - }, - { - "name": "forms", - "path": "apps-external/forms", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_forms_app" - }, - { - "name": "groupfolders", - "path": "apps-external/groupfolders", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_groupfolders_app" - }, - { - "name": "integration_openai", - "path": "apps-external/integration_openai", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_integration_openai_app" - }, - { - "name": "mail", - "path": "apps-external/mail", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_mail_app" - }, - { - "name": "ncw_apps_menu", - "path": "apps-external/ncw_apps_menu", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_ncw_apps_menu_app" - }, - { - "name": "ncw_mailtemplate", - "path": "apps-external/ncw_mailtemplate", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_ncw_mailtemplate_app" - }, - { - "name": "notes", - "path": "apps-external/notes", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_notes_app" - }, - { - "name": "notifications", - "path": "apps-external/notifications", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_notifications_app" - }, - { - "name": "notify_push", - "path": "apps-external/notify_push", - "has_npm": false, - "has_composer": true, - "makefile_target": "build_notify_push_app" - }, - { - "name": "password_policy", - "path": "apps-external/password_policy", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_password_policy_app" - }, - { - "name": "richdocuments", - "path": "apps-external/richdocuments", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_richdocuments_app" - }, - { - "name": "spreed", - "path": "apps-external/spreed", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_spreed_app" - }, - { - "name": "tables", - "path": "apps-external/tables", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_tables_app" - }, - { - "name": "tasks", - "path": "apps-external/tasks", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_tasks_app" - }, - { - "name": "text", - "path": "apps-external/text", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_text_app" - }, - { - "name": "twofactor_totp", - "path": "apps-external/twofactor_totp", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_twofactor_totp_app" - }, - { - "name": "user_oidc", - "path": "apps-external/user_oidc", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_user_oidc_app" - }, - { - "name": "viewer", - "path": "apps-external/viewer", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_viewer_app" - }, - { - "name": "whiteboard", - "path": "apps-external/whiteboard", - "has_npm": true, - "has_composer": true, - "makefile_target": "build_whiteboard_app" - } - ]' - - # Validate JSON and output as compact format - if echo "$matrix" | jq empty 2>/dev/null; then - echo "matrix=$(echo "$matrix" | jq -c '.')" >> $GITHUB_OUTPUT - echo "Matrix configuration set successfully" - else - echo "Error: Invalid JSON in matrix configuration" - exit 1 - fi - - - name: Validate matrix against Makefile - run: | - set +e # Intentionally allow script to continue on error for custom error handling and reporting to GITHUB_STEP_SUMMARY - set -u # Exit on undefined variable - - echo "### 🔍 Matrix Validation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Debug: Check if apps-external exists - echo "Checking apps-external directory..." - if [ ! -d "apps-external" ]; then - echo "❌ **Error:** apps-external directory does not exist!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Directory listing:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - ls -la >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - echo "Apps-external directory exists. Listing contents:" - ls -la apps-external/ | head -10 - - # Check if jq is available - echo "Checking if jq is installed..." - if ! command -v jq &> /dev/null; then - echo "❌ **Error:** jq is not installed!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "jq is required for matrix generation but was not found in PATH." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "jq version: $(jq --version)" - - echo "Generating matrix from Makefile..." - # Capture both stdout and stderr separately to better diagnose issues - makefile_output=$(make -f IONOS/Makefile generate_external_apps_matrix_json 2>&1) - makefile_exit_code=$? - - echo "Makefile exit code: ${makefile_exit_code}" - echo "Makefile output length: ${#makefile_output}" - - # Debug: Check if GITHUB_STEP_SUMMARY is set - echo "GITHUB_STEP_SUMMARY: ${GITHUB_STEP_SUMMARY:-NOT SET}" - - # If the Makefile command failed, show the error - if [ ${makefile_exit_code} -ne 0 ]; then - echo "" - echo "=== MAKEFILE ERROR ===" - echo "Exit code: ${makefile_exit_code}" - echo "Output:" - echo "$makefile_output" - echo "=====================" - echo "" - - # Write to summary - echo "❌ **Error:** Makefile command failed with exit code ${makefile_exit_code}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "Makefile error output" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$makefile_output" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - - echo "Error written to summary file: ${GITHUB_STEP_SUMMARY}" - exit 1 - fi - - # Filter out the info message to get just the JSON - # The Makefile outputs "[i] Generating..." to stderr, but we captured everything with 2>&1 - # So we need to extract just the JSON part - generated_matrix=$(echo "$makefile_output" | grep -v '^\[i\]' || echo "$makefile_output") - - workflow_matrix='${{ steps.set-matrix.outputs.matrix }}' - - # Debug output - echo "Generated matrix length: ${#generated_matrix}" - echo "Workflow matrix length: ${#workflow_matrix}" - - # Show first 200 chars of generated matrix for debugging - if [ -n "$generated_matrix" ]; then - echo "Generated matrix preview: ${generated_matrix:0:200}..." - fi - - # Validate that we got valid JSON - if ! echo "$generated_matrix" | jq empty 2>/dev/null; then - echo "❌ **Error:** Generated matrix is not valid JSON" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "Invalid JSON output" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$generated_matrix" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - # Validate that we got data - if [ -z "$generated_matrix" ] || [ -z "$workflow_matrix" ]; then - echo "❌ **Error:** Failed to load matrices" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- Generated matrix empty: $([ -z "$generated_matrix" ] && echo "yes" || echo "no")" >> $GITHUB_STEP_SUMMARY - echo "- Workflow matrix empty: $([ -z "$workflow_matrix" ] && echo "yes" || echo "no")" >> $GITHUB_STEP_SUMMARY - echo "- Makefile exit code: ${makefile_exit_code}" >> $GITHUB_STEP_SUMMARY - - exit 1 - fi - - # Sort both matrices for comparison - generated_sorted=$(echo "$generated_matrix" | jq -S '.' 2>&1 || echo "ERROR") - workflow_sorted=$(echo "$workflow_matrix" | jq -S '.' 2>&1 || echo "ERROR") - - echo "Sorted matrix lengths - generated: ${#generated_sorted}, workflow: ${#workflow_sorted}" - - # Compare the two matrices - if [ "$generated_sorted" = "$workflow_sorted" ]; then - echo "✅ **Validation passed!** The workflow matrix matches the Makefile configuration." >> $GITHUB_STEP_SUMMARY - echo "" - echo "✅ Matrix validation passed!" - else - echo "❌ **Validation failed!** The workflow matrix does not match the Makefile configuration." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - echo "Starting detailed comparison..." - - # Extract app names from both matrices - generated_apps=$(echo "$generated_matrix" | jq -r '.[].name' 2>/dev/null | sort || echo "") - workflow_apps=$(echo "$workflow_matrix" | jq -r '.[].name' 2>/dev/null | sort || echo "") - - echo "Generated apps count: $(echo "$generated_apps" | wc -l)" - echo "Workflow apps count: $(echo "$workflow_apps" | wc -l)" - - # Find missing apps (in Makefile but not in workflow) - missing_apps=$(comm -23 <(echo "$generated_apps") <(echo "$workflow_apps")) - if [ $? -ne 0 ]; then - echo "Error: comm command failed when finding missing apps." >&2 - exit 1 - fi - # Find extra apps (in workflow but not in Makefile) - extra_apps=$(comm -13 <(echo "$generated_apps") <(echo "$workflow_apps")) - if [ $? -ne 0 ]; then - echo "Error: comm command failed when finding extra apps." >&2 - exit 1 - fi - - echo "Missing apps: ${missing_apps:-none}" - echo "Extra apps: ${extra_apps:-none}" - - if [ -n "$missing_apps" ]; then - echo "#### ⚠️ Missing Apps" >> $GITHUB_STEP_SUMMARY - echo "The following apps are configured in the Makefile but missing from the workflow:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$missing_apps" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi - - if [ -n "$extra_apps" ]; then - echo "#### ⚠️ Extra Apps" >> $GITHUB_STEP_SUMMARY - echo "The following apps are in the workflow but not configured in the Makefile:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$extra_apps" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi - - # Check for configuration mismatches in common apps - common_apps=$(comm -12 <(echo "$generated_apps") <(echo "$workflow_apps") 2>/dev/null || echo "") - - echo "Common apps count: $(echo "$common_apps" | wc -l)" - - if [ -n "$common_apps" ]; then - mismatched_apps="" - - while IFS= read -r app; do - [ -z "$app" ] && continue - gen_config=$(echo "$generated_matrix" | jq -c --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "") - wf_config=$(echo "$workflow_matrix" | jq -c --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "") - - if [ -n "$gen_config" ] && [ -n "$wf_config" ] && [ "$gen_config" != "$wf_config" ]; then - mismatched_apps="${mismatched_apps}${app}"$'\n' - fi - done <<< "$common_apps" - - echo "Mismatched apps: ${mismatched_apps:-none}" - - if [ -n "$mismatched_apps" ]; then - echo "#### ⚠️ Configuration Mismatches" >> $GITHUB_STEP_SUMMARY - echo "The following apps have different configurations (has_npm, has_composer, etc.):" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$mismatched_apps" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "📋 Detailed differences" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```diff' >> $GITHUB_STEP_SUMMARY - - while IFS= read -r app; do - [ -z "$app" ] && continue - echo "=== $app ===" >> $GITHUB_STEP_SUMMARY - diff -u --label "Workflow" --label "Makefile" \ - <(echo "$workflow_matrix" | jq --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "{}") \ - <(echo "$generated_matrix" | jq --arg app "$app" '.[] | select(.name == $app)' 2>/dev/null || echo "{}") \ - >> $GITHUB_STEP_SUMMARY 2>&1 || true - done <<< "$mismatched_apps" - - echo '```' >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi - fi - - # Provide fix instructions - echo "#### 🔧 How to Fix" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Run this command locally to generate the correct matrix:" >> $GITHUB_STEP_SUMMARY - echo '```bash' >> $GITHUB_STEP_SUMMARY - echo "make -f IONOS/Makefile generate_external_apps_matrix_json" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Then update the \`matrix\` variable in \`.github/workflows/build-artifact.yml\` in the set-matrix step with the generated output." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Show full diff in expandable section - echo "
" >> $GITHUB_STEP_SUMMARY - echo "📄 Full matrix comparison" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Workflow Matrix:**" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - echo "$workflow_matrix" | jq '.' 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "$workflow_matrix" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Makefile Matrix:**" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - echo "$generated_matrix" | jq '.' 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "$generated_matrix" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - - echo "" - echo "❌ ERROR: Matrix validation failed!" - echo "See the job summary for details on what's wrong and how to fix it." - echo "Summary file size: $(wc -c < $GITHUB_STEP_SUMMARY || echo 0) bytes" - exit 1 - fi - - build-external-apps: - runs-on: ubuntu-latest - needs: prepare-matrix - - permissions: - contents: read - - name: build-external-apps - strategy: - max-parallel: 20 - matrix: - app: ${{ fromJson(needs.prepare-matrix.outputs.external-apps-matrix) }} - - steps: - - name: Checkout server - uses: actions/checkout@v5 - with: - submodules: true - fetch-depth: '1' - - - name: Set up node with version from package.json's engines - if: matrix.app.has_npm - uses: actions/setup-node@v5 - with: - node-version-file: "package.json" - cache: 'npm' - cache-dependency-path: ${{ matrix.app.path }}/package-lock.json - - - name: Setup PHP with PECL extension - if: matrix.app.has_composer - uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 #v2.31.1 - with: - tools: composer:v2 - extensions: gd, zip, curl, xml, xmlrpc, mbstring, sqlite, xdebug, pgsql, intl, imagick, gmp, apcu, bcmath, redis, soap, imap, opcache - env: - runner: ubuntu-latest - - - name: Cache Composer dependencies for ${{ matrix.app.name }} - if: matrix.app.has_composer - uses: actions/cache@v4 - with: - path: ${{ matrix.app.path }}/vendor - key: ${{ runner.os }}-composer-${{ matrix.app.name }}-${{ hashFiles(format('{0}/composer.lock', matrix.app.path)) }} - restore-keys: | - ${{ runner.os }}-composer-${{ matrix.app.name }}- - - - name: Build ${{ matrix.app.name }} app - run: make -f IONOS/Makefile ${{ matrix.app.makefile_target }} - - - name: Upload ${{ matrix.app.name }} build artifacts - uses: actions/upload-artifact@v4 - with: - retention-days: 1 - name: external-app-build-${{ matrix.app.name }} - path: | - ${{ matrix.app.path }} - !${{ matrix.app.path }}/node_modules - - build-artifact: - runs-on: ubuntu-latest - needs: [prepare-matrix, build-external-apps] - - permissions: - contents: read - - outputs: - NC_VERSION: ${{ steps.get_nc_version.outputs.NC_VERSION }} - - name: build-artifact - steps: - - name: Checkout server - uses: actions/checkout@v5 - with: - submodules: true - fetch-depth: '1' - - - name: Download build external apps - uses: actions/download-artifact@v5 - with: - pattern: external-app-build-* - path: apps-external/ - - - name: Reorganize downloaded apps-external artifacts - run: | - cd apps-external/ - - echo "Initial structure:" - ls -la - - # Move contents from external-app-build-* directories to their target directories - for artifact_dir in external-app-build-*; do - if [ -d "$artifact_dir" ]; then - # Extract app name from artifact directory name - app_name=${artifact_dir#external-app-build-} - - echo "Processing artifact: $artifact_dir -> $app_name" - - # If target directory exists, merge the contents from the artifact directory containing build artifacts - if [ -d "$app_name" ]; then - echo "Target directory $app_name exists, merging contents from $artifact_dir" - # Copy contents from artifact directory to target directory - cp -r "$artifact_dir"/* "$app_name"/ - # Remove the now-empty artifact directory - rm -rf "$artifact_dir" - else - # Move the artifact directory to the proper app name - echo "Moving $artifact_dir to $app_name" - mv "$artifact_dir" "$app_name" - fi - fi - done - - echo "Reorganization complete. Final structure:" - ls -la - - - name: Verify downloaded artifacts structure - run: | - echo "External apps structure:" - ls -la apps-external/ - for app_dir in apps-external/*/; do - if [ -d "$app_dir" ]; then - echo "Contents of $app_dir:" - ls -la "$app_dir" - fi - done - - - name: Set up node with version from package.json's engines - uses: actions/setup-node@v5 - with: - node-version-file: "package.json" - cache: 'npm' - - - name: Install Dependencies - run: sudo apt-get update && sudo apt-get install -y make zip unzip - - - name: Print dependencies versions - run: make --version && node --version && npm --version - - - name: Setup PHP with PECL extension - uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 #v2.31.1 - with: - tools: composer:v2 - extensions: gd, zip, curl, xml, xmlrpc, mbstring, sqlite, xdebug, pgsql, intl, imagick, gmp, apcu, bcmath, redis, soap, imap, opcache - env: - runner: ubuntu-latest - - - name: Cache Composer dependencies - uses: actions/cache@v4 - with: - path: vendor - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- - - - name: Print PHP install - run: php -i && php -m - - - name: Build Nextcloud - run: make -f IONOS/Makefile build_ncw - - - name: Add config partials - run: make -f IONOS/Makefile add_config_partials - - - name: Zip dependencies - run: make -f IONOS/Makefile zip_dependencies TARGET_PACKAGE_NAME=${{ env.TARGET_PACKAGE_NAME }} - - - name: Get NC version - id: get_nc_version - continue-on-error: false - run: | - NC_VERSION=$(jq -r '.ncVersion' version.json) - echo "NC_VERSION: $NC_VERSION" - - if [ -z "$NC_VERSION" ]; then - echo "NC_VERSION is empty" - exit 1 - fi - - echo "NC_VERSION=$NC_VERSION" >> $GITHUB_OUTPUT - - - name: Upload artifact result for job build-artifact - uses: actions/upload-artifact@v4 - with: - retention-days: 30 - name: nextcloud_workspace_build_artifact - path: ${{ env.TARGET_PACKAGE_NAME }} - - - name: Show changes on failure - if: failure() - run: | - git status - git --no-pager diff - exit 1 # make it red to grab attention - - upload-to-artifactory: - runs-on: self-hosted - # Upload the artifact to the Artifactory repository on PR *OR* on "ionos-dev|ionos-stable" branch push defined in the on:push:branches - if: github.event_name == 'pull_request' || github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' - - name: Push to artifactory - needs: [prepare-matrix, build-external-apps, build-artifact] - - outputs: - ARTIFACTORY_LAST_BUILD_PATH: ${{ steps.artifactory_upload.outputs.ARTIFACTORY_LAST_BUILD_PATH }} - - env: - BUILD_NAME: "nextcloud-workspace-snapshot" - - steps: - - name: Check prerequisites - run: | - # count the number of secrets that are set - echo "Checking if required secrets are set..." - error_count=0 - - if [ -z "${{ secrets.JF_ARTIFACTORY_URL }}" ]; then - # output error to github actions log - echo "::error::JF_ARTIFACTORY_URL secret is not set" - error_count=$((error_count + 1)) - fi - - if [ -z "${{ secrets.JF_ARTIFACTORY_USER }}" ]; then - echo "::error::JF_ARTIFACTORY_USER secret is not set" - error_count=$((error_count + 1)) - fi - - if [ -z "${{ secrets.JF_ACCESS_TOKEN }}" ]; then - echo "::error::JF_ACCESS_TOKEN secret is not set" - error_count=$((error_count + 1)) - fi - - # abort if any of the required secrets are not set - if [ $error_count -ne 0 ]; then - echo "::error::Required secrets are not set. Aborting." - exit 1 - fi - - - name: Download artifact zip - uses: actions/download-artifact@v5 - with: - name: nextcloud_workspace_build_artifact - - # This action sets up the JFrog CLI with the Artifactory URL and access token - - uses: jfrog/setup-jfrog-cli@7c95feb32008765e1b4e626b078dfd897c4340ad # v4.4.1 - env: - JF_URL: ${{ secrets.JF_ARTIFACTORY_URL }} - JF_USER: ${{ secrets.JF_ARTIFACTORY_USER }} - JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }} - - - name: Ping the JF server - run: | - # Ping the server - jf rt ping - - - name: Upload build to artifactory - id: artifactory_upload - run: | - # PR builds are stored in a separate directory as "dev/pr/nextcloud-workspace-pr-.zip" - # Push to "ionos-dev" branch is stored as "dev/nextcloud-workspace-.zip" - - ARTIFACTORY_STAGE_PREFIX="dev" - - # set ARTIFACTORY_STAGE_PREFIX=stable on ionos-stable branch - if [ "${{ github.ref_name }}" == "ionos-stable" ]; then - ARTIFACTORY_STAGE_PREFIX="stable" - fi - - export PATH_TO_DIRECTORY="${{ env.ARTIFACTORY_REPOSITORY_SNAPSHOT }}/${ARTIFACTORY_STAGE_PREFIX}" - PATH_TO_FILE="pr/nextcloud-workspace-pr-${{ github.event.pull_request.number }}-original.zip" - - if [ -z "${{ github.event.pull_request.number }}" ]; then - PATH_TO_FILE="nextcloud-workspace-${{ needs.build-artifact.outputs.NC_VERSION }}-original.zip" - fi - - export PATH_TO_LATEST_ARTIFACT="${PATH_TO_DIRECTORY}/${PATH_TO_FILE}" - - # Promote current build to the "latest" dev build - jf rt upload "${{ env.TARGET_PACKAGE_NAME }}" \ - --build-name "${{ env.BUILD_NAME }}" \ - --build-number ${{ github.run_number }} \ - --target-props "build.nc_version=${{ needs.build-artifact.outputs.NC_VERSION }};vcs.branch=${{ github.ref }};vcs.revision=${{ github.sha }}" \ - $PATH_TO_LATEST_ARTIFACT - - echo "ARTIFACTORY_LAST_BUILD_PATH=${PATH_TO_LATEST_ARTIFACT}" >> $GITHUB_OUTPUT - - - name: Show changes on failure - if: failure() - run: | - git status - git --no-pager diff - exit 1 # make it red to grab attention - - nextcloud-workspace-artifact-to-ghcr_io: - runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - - name: Push artifact to ghcr.io - needs: [prepare-matrix, build-external-apps, build-artifact] - - steps: - - name: Download artifact zip - uses: actions/download-artifact@v5 - with: - name: nextcloud_workspace_build_artifact - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" - - - name: Create Dockerfile - run: | - cat >Dockerfile << EOF - FROM busybox as builder - COPY ./${{ env.TARGET_PACKAGE_NAME }} / - WORKDIR /builder - RUN unzip /${{ env.TARGET_PACKAGE_NAME }} -d /builder - - FROM scratch - WORKDIR /app - VOLUME /app - COPY --from=builder /builder /app - EOF - - - name: Build and push Docker image - uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Show changes on failure - if: failure() - run: | - echo "Git status:" - git status - echo "Git diff:" - git diff - exit 1 # make it red to grab attention - - trigger-remote-dev-worflow: - runs-on: self-hosted - - name: Trigger remote workflow - needs: [ build-artifact, upload-to-artifactory ] - # Trigger remote build on "ionos-dev|ionos-stable" branch *push* defined in the on:push:branches - if: github.event_name == 'push' && ( github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' ) - steps: - - name: Trigger remote workflow - run: | - # Enable command echo for debugging purposes - set -x - - # Determine build type based on branch: - # - 'ionos-dev' branch triggers 'dev' build type - # - 'ionos-stable' branch triggers 'stable' build type - BUILD_TYPE="dev" - - # Override build type for stable branch - if [ "${{ github.ref_name }}" == "ionos-stable" ]; then - BUILD_TYPE="stable" - fi - - # Trigger GitLab pipeline via webhook with build artifacts and metadata - # Passes GitHub context variables to remote GitLab workflow - curl \ - --silent \ - --insecure \ - --request POST \ - --fail-with-body \ - -o response.json \ - --form token=${{ secrets.GITLAB_TOKEN }} \ - --form ref="stable" \ - --form "variables[GITHUB_SHA]=${{ github.sha }}" \ - --form "variables[ARTIFACTORY_LAST_BUILD_PATH]=${{ needs.upload-to-artifactory.outputs.ARTIFACTORY_LAST_BUILD_PATH }}" \ - --form "variables[NC_VERSION]=${{ needs.build-artifact.outputs.NC_VERSION }}" \ - --form "variables[BUILD_ID]=${{ github.run_id }}" \ - --form "variables[BUILD_TYPE]=${BUILD_TYPE}" \ - "${{ secrets.GITLAB_TRIGGER_URL }}" || ( RETCODE="$?"; jq . response.json; exit "$RETCODE" ) - - # Disable command echo - set +x - - # Print and parse json - # jq . response.json - echo "json<> $GITHUB_OUTPUT - cat response.json >> $GITHUB_OUTPUT - echo "END" >> $GITHUB_OUTPUT - echo "web_url<> $GITHUB_OUTPUT - cat response.json | jq --raw-output '.web_url' >> $GITHUB_OUTPUT - echo "END" >> $GITHUB_OUTPUT - - - name: Show changes on failure - if: failure() - run: | - git status - git --no-pager diff - exit 1 # make it red to grab attention