forked from nextcloud/server
-
Notifications
You must be signed in to change notification settings - Fork 0
Kh/dev/cache build pipeline #156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
7de1659
IONOS(build): copy build-artifact.yml as build-artifact-original.yml
printminion-co 072d18b
IONOS(build): update artifact filename to include '-original' suffix
printminion-co e1abd2e
IONOS(build): update fetch-depth in build-artifact.yml to integer format
printminion-co a469b66
IONOS(build): update action versions for improved functionality and c…
printminion-co f35caa8
IONOS(build): add retry logic for GitLab pipeline trigger
printminion-co 154f501
IONOS(build): add retry logic for artifact upload in build-artifact.yml
printminion-co 65a0236
IONOS(build): fix step ID naming in build-artifact.yml for consistency
printminion-co 72260a1
IONOS(build): generate apps matrix dynamically from Makefile
printminion-co 8df5198
IONOS(build): optimize build workflow - only rebuild changed apps
printminion-co 157075f
IONOS(build): ensure jobs depend on successful matrix preparation
printminion-co 179e10d
IONOS(build): add manual trigger for comparison testing in build-arti…
printminion-co 92283ce
IONOS(ci): Add app cache detection for JFrog build pipeline
bromiesTM 5f3b54f
IONOS(ci): Refactor cache detection logic into separate script
bromiesTM 3c0eea8
IONOS(ci): use cache version env for jfrog invalidation
bromiesTM ee46a9f
IONOS(ci): fix typos and spacing
bromiesTM File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,296 @@ | ||
| #!/bin/bash | ||
|
|
||
| # SPDX-FileCopyrightText: 2025 STRATO AG | ||
| # SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
|
||
| # Script to detect which apps need building vs. can be restored from cache | ||
| # Supports multiple cache sources: GitHub Actions cache and JFrog Artifactory | ||
| # Outputs JSON arrays for apps to build and apps to restore | ||
|
|
||
| set -e # Exit on error | ||
| set -u # Exit on undefined variable | ||
| set -o pipefail # Exit if any command in pipeline fails | ||
|
|
||
| # Required environment variables | ||
| : "${GH_TOKEN:?GH_TOKEN not set}" | ||
| : "${CACHE_VERSION:?CACHE_VERSION not set}" | ||
| : "${FORCE_REBUILD:?FORCE_REBUILD not set}" | ||
| : "${ARTIFACTORY_REPOSITORY_SNAPSHOT:?ARTIFACTORY_REPOSITORY_SNAPSHOT not set}" | ||
|
|
||
| # Optional JFrog variables | ||
| JF_URL="${JF_URL:-}" | ||
| JF_USER="${JF_USER:-}" | ||
| JF_ACCESS_TOKEN="${JF_ACCESS_TOKEN:-}" | ||
|
|
||
| # Input: MATRIX (JSON array of app configurations) | ||
| # Input: GITHUB_REF (current GitHub ref) | ||
| # Input: GITHUB_STEP_SUMMARY (path to step summary file) | ||
|
|
||
| # Outputs to $GITHUB_OUTPUT: | ||
| # - apps_to_build: JSON array of apps that need building | ||
| # - apps_to_restore: JSON array of apps that can be restored from cache | ||
| # - apps_sha_map: JSON object mapping app names to their SHAs | ||
| # - has_apps_to_build: boolean flag | ||
| # - has_apps_to_restore: boolean flag | ||
|
|
||
| echo "Collecting app SHAs and checking cache status..." | ||
| echo "Force rebuild mode: $FORCE_REBUILD" | ||
| echo "" | ||
|
|
||
| # Setup JFrog CLI if credentials are available | ||
| JFROG_AVAILABLE="false" | ||
| echo "=== JFrog Setup ===" | ||
| echo "JF_URL present: $([ -n "$JF_URL" ] && echo 'YES' || echo 'NO')" | ||
| echo "JF_USER present: $([ -n "$JF_USER" ] && echo 'YES' || echo 'NO')" | ||
| echo "JF_ACCESS_TOKEN present: $([ -n "$JF_ACCESS_TOKEN" ] && echo 'YES' || echo 'NO')" | ||
|
|
||
| if [ -n "$JF_URL" ] && [ -n "$JF_USER" ] && [ -n "$JF_ACCESS_TOKEN" ]; then | ||
| echo "✓ All JFrog credentials available" | ||
| echo "Installing JFrog CLI..." | ||
| # Install JFrog CLI | ||
| curl -fL https://install-cli.jfrog.io | sh | ||
| export PATH=$PATH:$PWD | ||
| echo "JFrog CLI version: $(jf --version)" | ||
|
|
||
| # Configure JFrog | ||
| echo "Configuring JFrog server: $JF_URL" | ||
| jf config add jfrog-server --url="$JF_URL" --user="$JF_USER" --access-token="$JF_ACCESS_TOKEN" --interactive=false | ||
|
|
||
| # Test connection with verbose output | ||
| echo "Testing JFrog connection..." | ||
| if jf rt ping; then | ||
| JFROG_AVAILABLE="true" | ||
| echo "✓ JFrog connection successful" | ||
| echo "Repository: $ARTIFACTORY_REPOSITORY_SNAPSHOT" | ||
| else | ||
| echo "⚠ JFrog ping failed, will fall back to GitHub cache" | ||
| echo "Ping output was unsuccessful" | ||
| fi | ||
| else | ||
| echo "⚠ JFrog credentials not available, using GitHub cache only" | ||
| [ -z "$JF_URL" ] && echo " - Missing: JF_URL" | ||
| [ -z "$JF_USER" ] && echo " - Missing: JF_USER" | ||
| [ -z "$JF_ACCESS_TOKEN" ] && echo " - Missing: JF_ACCESS_TOKEN" | ||
| fi | ||
| echo "JFROG_AVAILABLE=$JFROG_AVAILABLE" | ||
| echo "===================" | ||
| echo "" | ||
|
|
||
| # Get the matrix from input (passed as argument) | ||
| MATRIX="$1" | ||
|
|
||
| # Build JSON arrays for apps that need building/restoring | ||
| APPS_TO_BUILD="[]" | ||
| APPS_TO_RESTORE="[]" | ||
| APPS_CHECKED=0 | ||
| APPS_CACHED=0 | ||
| APPS_IN_JFROG=0 | ||
| APPS_TO_BUILD_COUNT=0 | ||
| APPS_TO_RESTORE_COUNT=0 | ||
| APPS_SHA_MAP="{}" | ||
| echo "" | ||
|
|
||
| echo "### 📦 Cache Status Report for ($GITHUB_REF)" >> "$GITHUB_STEP_SUMMARY" | ||
| echo "" >> "$GITHUB_STEP_SUMMARY" | ||
| if [ "$FORCE_REBUILD" == "true" ]; then | ||
| echo "**🔄 FORCE REBUILD MODE ENABLED** - All caches bypassed" >> "$GITHUB_STEP_SUMMARY" | ||
| echo "" >> "$GITHUB_STEP_SUMMARY" | ||
| fi | ||
| if [ "$JFROG_AVAILABLE" == "true" ]; then | ||
| echo "**🎯 JFrog Artifact Cache**: Enabled for all branches" >> "$GITHUB_STEP_SUMMARY" | ||
| echo "" >> "$GITHUB_STEP_SUMMARY" | ||
| fi | ||
| 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: <version>-app-build-<app-name>-<sha> | ||
| CACHE_KEY="${CACHE_VERSION}-app-build-${APP_NAME}-${CURRENT_SHA}" | ||
| SHORT_SHA="${CURRENT_SHA:0:8}" | ||
|
|
||
| echo -n " Checking $APP_NAME (SHA: $SHORT_SHA)... " | ||
|
|
||
| # If force rebuild is enabled, skip cache check and rebuild everything | ||
| if [ "$FORCE_REBUILD" == "true" ]; then | ||
| echo "🔄 force rebuild" | ||
| echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | 🔄 Force rebuild |" >> "$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 | ||
|
|
||
| # Check JFrog first before GitHub cache (available for all branches) | ||
| if [ "$JFROG_AVAILABLE" == "true" ]; then | ||
| JFROG_PATH="${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/${CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${CURRENT_SHA}.tar.gz" | ||
|
|
||
| echo "" | ||
| echo " 🔍 Checking JFrog for $APP_NAME..." | ||
| echo " Path: $JFROG_PATH" | ||
| echo " Full SHA: $CURRENT_SHA" | ||
|
|
||
| # Check if artifact exists in JFrog with verbose output | ||
| echo " Running: jf rt s \"$JFROG_PATH\"" | ||
| SEARCH_OUTPUT=$(jf rt s "$JFROG_PATH" 2>&1) | ||
| SEARCH_EXIT_CODE=$? | ||
|
|
||
| echo " Search exit code: $SEARCH_EXIT_CODE" | ||
| if [ $SEARCH_EXIT_CODE -eq 0 ]; then | ||
| echo " Search output:" | ||
| echo "$SEARCH_OUTPUT" | sed 's/^/ /' | ||
|
|
||
| if echo "$SEARCH_OUTPUT" | grep -q "$JFROG_PATH"; then | ||
| echo " ✓ Artifact found in JFrog!" | ||
| echo "✓ in JFrog" | ||
| echo "| $APP_NAME | \`$SHORT_SHA\` | \`$JFROG_PATH\` | 📦 In JFrog |" >> "$GITHUB_STEP_SUMMARY" | ||
| APPS_IN_JFROG=$((APPS_IN_JFROG + 1)) | ||
| APPS_TO_RESTORE_COUNT=$((APPS_TO_RESTORE_COUNT + 1)) | ||
| # Add to restore list with JFrog source | ||
| APPS_TO_RESTORE=$(echo "$APPS_TO_RESTORE" | jq -c --argjson app "$app_json" --arg sha "$CURRENT_SHA" --arg jfrog_path "$JFROG_PATH" --arg source "jfrog" '. + [($app + {sha: $sha, jfrog_path: $jfrog_path, source: $source})]') | ||
| continue | ||
| else | ||
| echo " ✗ Artifact not found in search results" | ||
| fi | ||
| else | ||
| echo " ✗ Search failed with error:" | ||
| echo "$SEARCH_OUTPUT" | sed 's/^/ /' | ||
| fi | ||
| echo " → Falling back to GitHub cache check" | ||
| fi | ||
|
|
||
| # Check if cache exists using GitHub CLI | ||
| # Include --ref to access caches from the current ref (branch, PR, etc.) | ||
| CACHE_EXISTS="false" | ||
| if ! CACHE_LIST=$(gh cache list --ref "$GITHUB_REF" --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)) | ||
| APPS_TO_RESTORE_COUNT=$((APPS_TO_RESTORE_COUNT + 1)) | ||
| echo "✓ cached" | ||
| echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | ✅ Cached |" >> "$GITHUB_STEP_SUMMARY" | ||
| # Add to restore list with GitHub cache source | ||
| APPS_TO_RESTORE=$(echo "$APPS_TO_RESTORE" | jq -c --argjson app "$app_json" --arg sha "$CURRENT_SHA" --arg cache_key "$CACHE_KEY" --arg source "github-cache" '. + [($app + {sha: $sha, cache_key: $cache_key, source: $source})]') | ||
| 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 in JFrog: $APPS_IN_JFROG" >> "$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" | ||
|
|
||
| TOTAL_AVAILABLE=$((APPS_IN_JFROG + APPS_CACHED)) | ||
| if [ $TOTAL_AVAILABLE -gt 0 ] && [ $APPS_CHECKED -gt 0 ]; then | ||
| CACHE_HIT_PERCENT=$((TOTAL_AVAILABLE * 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 " In JFrog: $APPS_IN_JFROG" | ||
| echo " Cached: $APPS_CACHED" | ||
| echo " To build: $APPS_TO_BUILD_COUNT" | ||
|
|
||
| # Validate no duplicate apps in build and restore lists | ||
| BUILD_APPS=$(echo "$APPS_TO_BUILD" | jq -r '.[].name' | sort) | ||
| RESTORE_APPS=$(echo "$APPS_TO_RESTORE" | jq -r '.[].name' | sort) | ||
| DUPLICATE_APPS=$(comm -12 <(echo "$BUILD_APPS") <(echo "$RESTORE_APPS")) | ||
|
|
||
| if [ -n "$DUPLICATE_APPS" ]; then | ||
| echo "ERROR: Apps appear in both build and restore lists:" | ||
| echo "$DUPLICATE_APPS" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # 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<<APPS_TO_BUILD_JSON_EOF" >> "$GITHUB_OUTPUT" | ||
| echo "$APPS_TO_BUILD" >> "$GITHUB_OUTPUT" | ||
| echo "APPS_TO_BUILD_JSON_EOF" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # Output the unified list of apps to restore (from either GitHub cache or JFrog) | ||
| echo "apps_to_restore<<APPS_TO_RESTORE_JSON_EOF" >> "$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<<APPS_SHA_MAP_JSON_EOF" >> "$GITHUB_OUTPUT" | ||
| echo "$APPS_SHA_MAP" >> "$GITHUB_OUTPUT" | ||
| echo "APPS_SHA_MAP_JSON_EOF" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # Output flags for conditional job execution | ||
| if [ $APPS_TO_BUILD_COUNT -gt 0 ]; then | ||
| echo "has_apps_to_build=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "has_apps_to_build=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| if [ $APPS_TO_RESTORE_COUNT -gt 0 ]; then | ||
| echo "has_apps_to_restore=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "has_apps_to_restore=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 | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
--keyas a filter ingh cache listrequires an exact match, but the intention seems to be finding a cache with this specific key. Consider using--keyas a prefix matcher or verify that the exact key matching is intentional and document this behavior.