snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
This commit is contained in:
581
.github/actions/docker-image-size-tracker/action.yml
vendored
Normal file
581
.github/actions/docker-image-size-tracker/action.yml
vendored
Normal file
@@ -0,0 +1,581 @@
|
||||
name: 'Docker Image Size Tracker'
|
||||
description: 'Track and report Docker image sizes in Pull Requests'
|
||||
author: 'Rocket.Chat'
|
||||
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'GitHub token for commenting on PRs'
|
||||
required: true
|
||||
ci-pat:
|
||||
description: 'GitHub token for committing to history branch'
|
||||
required: true
|
||||
registry:
|
||||
description: 'Container registry (e.g., ghcr.io)'
|
||||
required: false
|
||||
default: 'ghcr.io'
|
||||
repository:
|
||||
description: 'Repository name (e.g., rocketchat)'
|
||||
required: true
|
||||
tag:
|
||||
description: 'Image tag to measure'
|
||||
required: true
|
||||
baseline-tag:
|
||||
description: 'Baseline tag to compare against'
|
||||
required: false
|
||||
default: 'develop'
|
||||
platform:
|
||||
description: 'Platform architecture to compare (amd64 or arm64)'
|
||||
required: false
|
||||
default: 'amd64'
|
||||
size-thresholds:
|
||||
description: 'Optional JSON: per-image trigger thresholds. Only comment when an image increase exceeds its threshold. Example: {"rocketchat":{"mb":50,"percent":5},"omnichannel":{"mb":10,"percent":2}}'
|
||||
required: false
|
||||
default: ''
|
||||
fail-thresholds:
|
||||
description: 'Optional JSON: per-image fail thresholds. Task fails when an image increase exceeds its threshold (mb and/or percent). Example: {"rocketchat":{"mb":100,"percent":15}}'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
outputs:
|
||||
total-size:
|
||||
description: 'Total size in bytes'
|
||||
value: ${{ steps.measure.outputs.total-size }}
|
||||
size-diff:
|
||||
description: 'Size difference in bytes'
|
||||
value: ${{ steps.compare.outputs.size-diff }}
|
||||
size-diff-percent:
|
||||
description: 'Size difference percentage'
|
||||
value: ${{ steps.compare.outputs.size-diff-percent }}
|
||||
comment-triggered:
|
||||
description: 'Whether to post PR comment (only when size is bigger and thresholds met)'
|
||||
value: ${{ steps.compare.outputs.comment-triggered }}
|
||||
failed:
|
||||
description: 'True if image size exceeded fail-thresholds'
|
||||
value: ${{ steps.compare.outputs.failed }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Download manifests
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
pattern: manifests-*
|
||||
path: /tmp/manifests
|
||||
merge-multiple: true
|
||||
|
||||
- name: Measure image sizes from artifacts
|
||||
id: measure
|
||||
shell: bash
|
||||
env:
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
echo "Reading image sizes from build artifacts for platform: $PLATFORM"
|
||||
|
||||
declare -A sizes
|
||||
declare -a services_list
|
||||
total=0
|
||||
|
||||
# Loop through service directories (same as publish workflow)
|
||||
shopt -s nullglob
|
||||
for service_dir in /tmp/manifests/*; do
|
||||
[[ -d "$service_dir" ]] || continue
|
||||
service="$(basename "$service_dir")"
|
||||
|
||||
echo "Processing service: $service"
|
||||
services_list+=("$service")
|
||||
|
||||
size=0
|
||||
# Read only the specified platform architecture
|
||||
manifest_file="$service_dir/$PLATFORM/manifest.json"
|
||||
if [[ -f "$manifest_file" ]]; then
|
||||
# docker manifest inspect -v puts the manifest under SchemaV2Manifest
|
||||
# (Docker schema2 images) or OCIManifest (OCI images, which buildx
|
||||
# produces by default). Coalesce so both formats are read.
|
||||
config_size=$(jq -r '(.SchemaV2Manifest // .OCIManifest).config.size // 0' "$manifest_file")
|
||||
layers_size=$(jq '[(.SchemaV2Manifest // .OCIManifest).layers[]?.size // 0] | add // 0' "$manifest_file")
|
||||
size=$((config_size + layers_size))
|
||||
|
||||
echo " → Found $manifest_file: $size bytes (config: $config_size, layers: $layers_size)"
|
||||
else
|
||||
echo " ⚠ Manifest not found for platform $PLATFORM: $manifest_file"
|
||||
fi
|
||||
|
||||
sizes[$service]=$size
|
||||
total=$((total + size))
|
||||
done
|
||||
|
||||
echo "Total size (all services, $PLATFORM only): $total bytes"
|
||||
echo "total-size=$total" >> $GITHUB_OUTPUT
|
||||
|
||||
# Save to JSON
|
||||
echo "{" > current-sizes.json
|
||||
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," >> current-sizes.json
|
||||
echo " \"tag\": \"$TAG\"," >> current-sizes.json
|
||||
echo " \"total\": $total," >> current-sizes.json
|
||||
echo " \"services\": {" >> current-sizes.json
|
||||
|
||||
first=true
|
||||
for service in "${services_list[@]}"; do
|
||||
if [[ "$first" == "true" ]]; then
|
||||
first=false
|
||||
else
|
||||
echo "," >> current-sizes.json
|
||||
fi
|
||||
echo " \"$service\": ${sizes[$service]}" >> current-sizes.json
|
||||
done
|
||||
|
||||
echo " }" >> current-sizes.json
|
||||
echo "}" >> current-sizes.json
|
||||
|
||||
echo "Current sizes saved:"
|
||||
cat current-sizes.json
|
||||
|
||||
# Save services list for baseline measurement
|
||||
printf '%s\n' "${services_list[@]}" > /tmp/services-list.txt
|
||||
|
||||
- name: Measure baseline
|
||||
id: baseline
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
env:
|
||||
REGISTRY: ${{ inputs.registry }}
|
||||
ORG: ${{ inputs.repository }}
|
||||
TAG: ${{ inputs.baseline-tag }}
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
run: |
|
||||
echo "Measuring baseline: $REGISTRY/$ORG/*:$TAG (platform: $PLATFORM)"
|
||||
|
||||
declare -A sizes
|
||||
declare -a services_list
|
||||
total=0
|
||||
|
||||
# Read services list from current measurement
|
||||
while IFS= read -r service; do
|
||||
services_list+=("$service")
|
||||
|
||||
# Map service name to image name (handle rocketchat -> rocket.chat)
|
||||
if [[ "$service" == "rocketchat" ]] || [[ "$service" == "rocketchat-cov" ]]; then
|
||||
image_name="rocket.chat"
|
||||
[[ "$service" == "rocketchat-cov" ]] && image_name="rocket.chat-cov"
|
||||
else
|
||||
image_name="$service"
|
||||
fi
|
||||
|
||||
image="$REGISTRY/$ORG/$image_name:$TAG"
|
||||
echo " → Inspecting $image"
|
||||
|
||||
size=0
|
||||
if manifest=$(docker manifest inspect "$image" 2>/dev/null); then
|
||||
# Check if it's a manifest list (multi-arch)
|
||||
if echo "$manifest" | jq -e '.manifests' > /dev/null 2>&1; then
|
||||
# Manifest list - find the specified platform
|
||||
echo " → Multi-arch manifest detected, filtering for $PLATFORM"
|
||||
platform_digest=$(echo "$manifest" | jq -r --arg arch "$PLATFORM" '.manifests[] | select(.platform.architecture == $arch) | .digest' | head -1)
|
||||
|
||||
if [[ -n "$platform_digest" ]]; then
|
||||
echo " → Inspecting $PLATFORM platform: $platform_digest"
|
||||
if platform_manifest=$(docker manifest inspect "$REGISTRY/$ORG/$image_name@$platform_digest" 2>/dev/null); then
|
||||
config_size=$(echo "$platform_manifest" | jq -r '.config.size // 0')
|
||||
layers_size=$(echo "$platform_manifest" | jq '[.layers[]?.size // 0] | add // 0')
|
||||
size=$((config_size + layers_size))
|
||||
echo " → Size: $size bytes"
|
||||
fi
|
||||
else
|
||||
echo " ⚠ Platform $PLATFORM not found in manifest"
|
||||
fi
|
||||
else
|
||||
# Single arch manifest
|
||||
config_size=$(echo "$manifest" | jq -r '.config.size // 0')
|
||||
layers_size=$(echo "$manifest" | jq '[.layers[]?.size // 0] | add // 0')
|
||||
size=$((config_size + layers_size))
|
||||
fi
|
||||
fi
|
||||
|
||||
sizes[$service]=$size
|
||||
total=$((total + size))
|
||||
done < /tmp/services-list.txt
|
||||
|
||||
echo "baseline-total=$total" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "{" > baseline-sizes.json
|
||||
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," >> baseline-sizes.json
|
||||
echo " \"tag\": \"$TAG\"," >> baseline-sizes.json
|
||||
echo " \"total\": $total," >> baseline-sizes.json
|
||||
echo " \"services\": {" >> baseline-sizes.json
|
||||
|
||||
first=true
|
||||
for service in "${services_list[@]}"; do
|
||||
if [[ "$first" == "true" ]]; then
|
||||
first=false
|
||||
else
|
||||
echo "," >> baseline-sizes.json
|
||||
fi
|
||||
echo " \"$service\": ${sizes[$service]}" >> baseline-sizes.json
|
||||
done
|
||||
|
||||
echo " }" >> baseline-sizes.json
|
||||
echo "}" >> baseline-sizes.json
|
||||
|
||||
- name: Setup history storage
|
||||
id: history
|
||||
shell: bash
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Create a separate worktree for history branch
|
||||
mkdir -p /tmp/history-worktree
|
||||
|
||||
# Try to fetch history branch
|
||||
if git ls-remote --heads origin image-size-history | grep -q image-size-history; then
|
||||
git fetch origin image-size-history
|
||||
git worktree add /tmp/history-worktree image-size-history
|
||||
else
|
||||
# Create orphan branch for history in worktree
|
||||
git worktree add --detach /tmp/history-worktree
|
||||
cd /tmp/history-worktree
|
||||
git checkout --orphan image-size-history
|
||||
git rm -rf . 2>/dev/null || true
|
||||
mkdir -p history
|
||||
echo "# Image Size History" > README.md
|
||||
echo "This branch stores historical image size data for tracking" >> README.md
|
||||
git add README.md
|
||||
git commit -m "Initialize image size history"
|
||||
git push origin image-size-history
|
||||
cd -
|
||||
fi
|
||||
|
||||
mkdir -p /tmp/history-worktree/history
|
||||
|
||||
- name: Load historical data
|
||||
shell: bash
|
||||
run: |
|
||||
# Load last 30 measurements and group by day (keep only last entry per day)
|
||||
echo "[]" > history-data.json
|
||||
if [[ -d /tmp/history-worktree/history ]]; then
|
||||
jq -s '.' /tmp/history-worktree/history/*.json 2>/dev/null | jq '
|
||||
sort_by(.timestamp) |
|
||||
group_by(.timestamp[0:10]) |
|
||||
map(.[-1]) |
|
||||
.[-30:]
|
||||
' > history-data.json || echo "[]" > history-data.json
|
||||
fi
|
||||
|
||||
count=$(jq 'length' history-data.json)
|
||||
echo "Loaded $count historical measurements (one per day)"
|
||||
|
||||
- name: Save current measurement to history
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
shell: bash
|
||||
env:
|
||||
CI_PAT: ${{ inputs.ci-pat }}
|
||||
run: |
|
||||
timestamp=$(date -u +%Y%m%d-%H%M%S)
|
||||
commit_sha="${{ github.sha }}"
|
||||
|
||||
# Add commit info to current measurement
|
||||
jq --arg sha "$commit_sha" '. + {commit: $sha}' current-sizes.json > "/tmp/history-worktree/history/${timestamp}.json"
|
||||
|
||||
cd /tmp/history-worktree
|
||||
git add "history/${timestamp}.json"
|
||||
git commit -m "Add measurement for ${timestamp} (${commit_sha:0:7})"
|
||||
git config --global user.email "ci@rocket.chat"
|
||||
git config --global user.name "rocketchat-ci[bot]"
|
||||
git config --global url.https://$CI_PAT@github.com/.insteadOf https://github.com/
|
||||
git push origin image-size-history
|
||||
cd -
|
||||
|
||||
echo "Saved measurement to history"
|
||||
|
||||
- name: Compare and generate report
|
||||
id: compare
|
||||
shell: bash
|
||||
env:
|
||||
SIZE_THRESHOLDS: ${{ inputs.size-thresholds }}
|
||||
FAIL_THRESHOLDS: ${{ inputs.fail-thresholds }}
|
||||
TAG: ${{ inputs.tag }}
|
||||
BASELINE_TAG: ${{ inputs.baseline-tag }}
|
||||
run: |
|
||||
current_total=$(jq -r '.total' current-sizes.json)
|
||||
|
||||
if [[ ! -f baseline-sizes.json ]]; then
|
||||
echo "No baseline available"
|
||||
echo "size-diff=0" >> $GITHUB_OUTPUT
|
||||
echo "size-diff-percent=0" >> $GITHUB_OUTPUT
|
||||
echo "comment-triggered=false" >> $GITHUB_OUTPUT
|
||||
|
||||
cat > report.md << 'EOF'
|
||||
# 📦 Docker Image Size Report
|
||||
|
||||
**Status:** First measurement - no baseline for comparison
|
||||
|
||||
**Total Size:** $(numfmt --to=iec-i --suffix=B $current_total)
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
baseline_total=$(jq -r '.total' baseline-sizes.json)
|
||||
diff=$((current_total - baseline_total))
|
||||
|
||||
if [[ $baseline_total -gt 0 ]]; then
|
||||
percent=$(awk "BEGIN {printf \"%.2f\", ($diff / $baseline_total) * 100}")
|
||||
else
|
||||
percent=0
|
||||
fi
|
||||
|
||||
echo "size-diff=$diff" >> $GITHUB_OUTPUT
|
||||
echo "size-diff-percent=$percent" >> $GITHUB_OUTPUT
|
||||
|
||||
# Only comment when size is bigger than baseline; optionally require per-image thresholds
|
||||
THRESHOLDS="$SIZE_THRESHOLDS"
|
||||
FAIL_THRESHOLDS="$FAIL_THRESHOLDS"
|
||||
comment_triggered=false
|
||||
fail_triggered=false
|
||||
if [[ $diff -gt 0 ]]; then
|
||||
if [[ -z "$THRESHOLDS" ]] || [[ "$THRESHOLDS" == "{}" ]]; then
|
||||
comment_triggered=true
|
||||
fi
|
||||
fi
|
||||
|
||||
color="gray"
|
||||
if (( $(awk "BEGIN {print ($percent > 0.01)}") )); then
|
||||
color="red"
|
||||
elif (( $(awk "BEGIN {print ($percent < -0.01)}") )); then
|
||||
color="green"
|
||||
fi
|
||||
|
||||
# Generate report
|
||||
if [[ $diff -gt 0 ]]; then
|
||||
emoji="📈"
|
||||
badge=""
|
||||
sign="+"
|
||||
elif [[ $diff -lt 0 ]]; then
|
||||
emoji="📉"
|
||||
badge=""
|
||||
sign="-"
|
||||
else
|
||||
emoji="➡️"
|
||||
badge=""
|
||||
sign=""
|
||||
fi
|
||||
|
||||
cat > report.md << EOF
|
||||
# 📦 Docker Image Size Report
|
||||
|
||||
## $emoji Changes $badge
|
||||
|
||||
| Service | Current | Baseline | Change | Percent |
|
||||
|---------|---------|----------|--------|---------|
|
||||
| **sum of all images** | **$(numfmt --to=iec-i --suffix=B $current_total)** | **$(numfmt --to=iec-i --suffix=B $baseline_total)** | **${sign}$(numfmt --to=iec-i --suffix=B ${diff#-})** | $badge |
|
||||
EOF
|
||||
|
||||
# Get services dynamically from current-sizes.json, sorted by size (largest first)
|
||||
for service in $(jq -r '.services | to_entries | sort_by(-.value) | .[].key' current-sizes.json); do
|
||||
current=$(jq -r ".services.\"$service\"" current-sizes.json)
|
||||
baseline=$(jq -r ".services.\"$service\" // 0" baseline-sizes.json)
|
||||
service_diff=$((current - baseline))
|
||||
|
||||
if [[ $baseline -gt 0 ]]; then
|
||||
service_percent=$(awk "BEGIN {printf \"%.2f\", ($service_diff / $baseline) * 100}")
|
||||
else
|
||||
service_percent=0
|
||||
fi
|
||||
|
||||
# Check per-image thresholds for comment trigger (only when size increased)
|
||||
if [[ $diff -gt 0 ]] && [[ -n "$THRESHOLDS" ]] && [[ "$THRESHOLDS" != "{}" ]]; then
|
||||
threshold_mb=$(echo "$THRESHOLDS" | jq -r ".\"$service\".mb // empty")
|
||||
threshold_pct=$(echo "$THRESHOLDS" | jq -r ".\"$service\".percent // empty")
|
||||
if [[ -n "$threshold_mb" ]] || [[ -n "$threshold_pct" ]]; then
|
||||
exceeded=false
|
||||
if [[ -n "$threshold_mb" ]] && [[ $service_diff -ge $(awk "BEGIN {printf \"%.0f\", $threshold_mb * 1048576}") ]]; then
|
||||
exceeded=true
|
||||
fi
|
||||
if [[ -n "$threshold_pct" ]] && [[ $service_percent != "0.00" ]] && (( $(awk "BEGIN {print ($service_percent >= $threshold_pct)}") )); then
|
||||
exceeded=true
|
||||
fi
|
||||
[[ "$exceeded" == "true" ]] && comment_triggered=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check per-image fail thresholds (task fails when exceeded)
|
||||
if [[ $diff -gt 0 ]] && [[ -n "$FAIL_THRESHOLDS" ]] && [[ "$FAIL_THRESHOLDS" != "{}" ]]; then
|
||||
fail_mb=$(echo "$FAIL_THRESHOLDS" | jq -r ".\"$service\".mb // empty")
|
||||
fail_pct=$(echo "$FAIL_THRESHOLDS" | jq -r ".\"$service\".percent // empty")
|
||||
if [[ -n "$fail_mb" ]] || [[ -n "$fail_pct" ]]; then
|
||||
exceeded=false
|
||||
if [[ -n "$fail_mb" ]] && [[ $service_diff -ge $(awk "BEGIN {printf \"%.0f\", $fail_mb * 1048576}") ]]; then
|
||||
exceeded=true
|
||||
fi
|
||||
if [[ -n "$fail_pct" ]] && [[ $service_percent != "0.00" ]] && (( $(awk "BEGIN {print ($service_percent >= $fail_pct)}") )); then
|
||||
exceeded=true
|
||||
fi
|
||||
[[ "$exceeded" == "true" ]] && fail_triggered=true
|
||||
fi
|
||||
fi
|
||||
|
||||
color="gray"
|
||||
if (( $(awk "BEGIN {print ($service_percent > 0.01)}") )); then
|
||||
color="red"
|
||||
elif (( $(awk "BEGIN {print ($service_percent < -0.01)}") )); then
|
||||
color="green"
|
||||
fi
|
||||
|
||||
if [[ $service_diff -gt 0 ]]; then
|
||||
badge=""
|
||||
sign="+"
|
||||
elif [[ $service_diff -lt 0 ]]; then
|
||||
badge=""
|
||||
sign="-"
|
||||
else
|
||||
badge=""
|
||||
sign=""
|
||||
fi
|
||||
|
||||
echo "| $service | $(numfmt --to=iec-i --suffix=B $current) | $(numfmt --to=iec-i --suffix=B $baseline) | ${sign}$(numfmt --to=iec-i --suffix=B ${service_diff#-}) | $badge |" >> report.md
|
||||
done
|
||||
|
||||
# Generate historical trend chart
|
||||
echo "" >> report.md
|
||||
echo "## 📊 Historical Trend" >> report.md
|
||||
echo "" >> report.md
|
||||
|
||||
# Load history and generate mermaid chart
|
||||
history_count=$(jq 'length' history-data.json)
|
||||
|
||||
if [[ $history_count -gt 0 ]]; then
|
||||
# Get all services from current build
|
||||
all_services=$(jq -r '.services | keys | .[]' current-sizes.json | sort)
|
||||
|
||||
# Generate Mermaid chart data
|
||||
x_labels=""
|
||||
declare -A service_data
|
||||
|
||||
# Initialize service data arrays
|
||||
for service in $all_services; do
|
||||
service_data[$service]=""
|
||||
done
|
||||
|
||||
# Process historical data
|
||||
while IFS= read -r line; do
|
||||
timestamp=$(echo "$line" | jq -r '.timestamp')
|
||||
date_label=$(date -d "$timestamp" +"%m/%d %H:%M")
|
||||
|
||||
if [[ -z "$x_labels" ]]; then
|
||||
x_labels="\"$date_label\""
|
||||
else
|
||||
x_labels="$x_labels, \"$date_label\""
|
||||
fi
|
||||
|
||||
# Add data point for each service
|
||||
for service in $all_services; do
|
||||
size=$(echo "$line" | jq -r ".services.\"$service\" // 0")
|
||||
size_gb=$(awk "BEGIN {printf \"%.2f\", $size / 1073741824}")
|
||||
|
||||
if [[ -z "${service_data[$service]}" ]]; then
|
||||
service_data[$service]="$size_gb"
|
||||
else
|
||||
service_data[$service]="${service_data[$service]}, $size_gb"
|
||||
fi
|
||||
done
|
||||
done < <(jq -c '.[]' history-data.json)
|
||||
|
||||
# Add current PR as last point
|
||||
current_date=$(date -u +"%m/%d %H:%M")
|
||||
x_labels="$x_labels, \"$current_date (PR)\""
|
||||
|
||||
for service in $all_services; do
|
||||
size=$(jq -r ".services.\"$service\" // 0" current-sizes.json)
|
||||
size_gb=$(awk "BEGIN {printf \"%.2f\", $size / 1073741824}")
|
||||
service_data[$service]="${service_data[$service]}, $size_gb"
|
||||
done
|
||||
|
||||
# Generate mermaid chart with multiple lines
|
||||
cat >> report.md << EOF
|
||||
\`\`\`mermaid
|
||||
---
|
||||
config:
|
||||
theme: "dark"
|
||||
xyChart:
|
||||
width: 900
|
||||
height: 400
|
||||
---
|
||||
xychart
|
||||
title "Image Size Evolution by Service (Last 30 Days + This PR)"
|
||||
x-axis [$x_labels]
|
||||
y-axis "Size (GB)" 0 --> 0.5
|
||||
EOF
|
||||
|
||||
# Add a line for each service
|
||||
for service in $all_services; do
|
||||
echo " line \"$service\" [${service_data[$service]}]" >> report.md
|
||||
done
|
||||
|
||||
cat >> report.md << 'EOF'
|
||||
```
|
||||
|
||||
EOF
|
||||
|
||||
# Add summary stats
|
||||
min_size=$(jq '[.[].total] | min' history-data.json)
|
||||
max_size=$(jq '[.[].total] | max' history-data.json)
|
||||
avg_size=$(jq '[.[].total] | add / length' history-data.json)
|
||||
|
||||
cat >> report.md << EOF
|
||||
**Statistics (last $history_count days):**
|
||||
- 📊 Average: $(numfmt --to=iec-i --suffix=B ${avg_size%.*})
|
||||
- ⬇️ Minimum: $(numfmt --to=iec-i --suffix=B $min_size)
|
||||
- ⬆️ Maximum: $(numfmt --to=iec-i --suffix=B $max_size)
|
||||
- 🎯 Current PR: $(numfmt --to=iec-i --suffix=B $current_total)
|
||||
|
||||
EOF
|
||||
|
||||
else
|
||||
cat >> report.md << 'EOF'
|
||||
*No historical data available yet. History tracking starts after merging to develop.*
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat >> report.md << EOF
|
||||
|
||||
<details>
|
||||
<summary>ℹ️ About this report</summary>
|
||||
|
||||
This report compares Docker image sizes from this build against the \`$BASELINE_TAG\` baseline.
|
||||
|
||||
- **Tag:** \`$TAG\`
|
||||
- **Baseline:** \`$BASELINE_TAG\`
|
||||
- **Timestamp:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
- **Historical data points:** $history_count
|
||||
|
||||
</details>
|
||||
EOF
|
||||
|
||||
echo "comment-triggered=$comment_triggered" >> $GITHUB_OUTPUT
|
||||
echo "failed=$fail_triggered" >> $GITHUB_OUTPUT
|
||||
|
||||
if [[ "$fail_triggered" == "true" ]]; then
|
||||
echo "::error::Docker image size exceeded fail-thresholds (mb/percent). Check the report for details."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Add report to Job Summary
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -f report.md ]]; then
|
||||
cat report.md >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Cleanup worktree
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -d /tmp/history-worktree ]]; then
|
||||
git worktree remove /tmp/history-worktree --force 2>/dev/null || true
|
||||
fi
|
||||
|
||||
branding:
|
||||
icon: 'package'
|
||||
color: 'blue'
|
||||
Reference in New Issue
Block a user