Skip to content

Update internal-dependencies #866

Update internal-dependencies

Update internal-dependencies #866

Workflow file for this run

name: Build Docker Images
on:
pull_request:
branches: [ main ]
paths-ignore:
- '**.md'
- 'docs/**'
- '.gitignore'
- 'LICENSE'
- '.github/**'
- '!.github/workflows/**'
types:
- opened # PR is created
- synchronize # New commits pushed
- reopened # Closed PR is reopened
pull_request_target:
branches: [ main ]
paths-ignore:
- '**.md'
- 'docs/**'
- '.gitignore'
- 'LICENSE'
- '.github/**'
- '!.github/workflows/**'
types:
- closed # PR is merged or closed
permissions:
contents: read
packages: write
id-token: write
pull-requests: write
env:
HOME: /home/github-runner
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
detect-changes:
runs-on: dev
if: |
github.event_name == 'pull_request' &&
github.event.action != 'closed'
outputs:
base_matrix: ${{ steps.set-matrix.outputs.base_matrix }}
clients_matrix: ${{ steps.set-matrix.outputs.clients_matrix }}
protocols_matrix: ${{ steps.set-matrix.outputs.protocols_matrix }}
content_changes: ${{ steps.set-matrix.outputs.content_changes }}
all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}
dockerfile_changes: ${{ steps.set-matrix.outputs.dockerfile_changes }}
dockerfile_matrix: ${{ steps.set-matrix.outputs.dockerfile_matrix }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0 # Fetch all history for all tags and branches
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@d6e91a2266cdb9d62096cebf1e8546899c6aa18f # v45
with:
base_sha: ${{ github.event.pull_request.base.sha }}
fetch_depth: 0
- name: List all changed files
env:
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
for file in ${ALL_CHANGED_FILES}; do
echo "$file was changed"
done
- name: Debug changed files outputs
env:
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
echo "Debug outputs from changed-files action:"
echo "Changed files:"
echo "$ALL_CHANGED_FILES"
- name: Generate build matrices
id: set-matrix
env:
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
# First find all Docker build contexts (directories containing Dockerfile)
DOCKER_DIRS=$(find . -name Dockerfile -exec dirname {} \; | sed 's|^./||' | sort)
echo "Found Docker directories:"
echo "$DOCKER_DIRS"
echo "Changed files in this commit:"
echo "$ALL_CHANGED_FILES"
# For each changed file, find which Docker directory it belongs to
CHANGED_DIRS=$(for file in ${ALL_CHANGED_FILES}; do
echo "Checking file: $file"
echo "$DOCKER_DIRS" | while read -r docker_dir; do
if [[ "$file" =~ ^"$docker_dir"(/|$) ]]; then
echo "$docker_dir"
fi
done
done | sort -u)
echo "Changed directories:"
echo "$CHANGED_DIRS"
# Filter by component
BASE=$(echo "$CHANGED_DIRS" | grep "^base-images/" || true)
CLIENTS=$(echo "$CHANGED_DIRS" | grep "^clients/" || true)
PROTOCOLS=$(echo "$CHANGED_DIRS" | grep "^protocols/[^/]\+/[^/]\+$" || true)
CONTENT_CHANGES=$(echo "$CHANGED_DIRS" | grep "^protocols/" || true)
DOCKERFILE_CHANGES=$(echo "$CHANGED_DIRS" | grep -E "^base-images/|^clients/|^protocols/" || true)
echo "Base dirs: $BASE"
echo "Client dirs: $CLIENTS"
echo "Protocol dirs: $PROTOCOLS"
echo "Content changes: $CONTENT_CHANGES"
echo "Dockerfile changes: $DOCKERFILE_CHANGES"
# Generate base image matrix from changed base-images directories
if [[ -n "$BASE" ]]; then
BASE_MATRIX=$(echo "$BASE" | jq -Rsc 'split("\n")[:-1] | {include: map({image_path: .})}')
else
BASE_MATRIX='{"include":[]}'
fi
echo "base_matrix=$BASE_MATRIX" >> $GITHUB_OUTPUT
if [[ -n "$CLIENTS" ]]; then
echo "clients_matrix=$(echo "$CLIENTS" | jq -Rsc 'split("\n")[:-1] | {include: map({image_path: .})}')" >> $GITHUB_OUTPUT
else
echo 'clients_matrix={"include":[]}' >> $GITHUB_OUTPUT
fi
if [[ -n "$PROTOCOLS" ]]; then
echo "protocols_matrix=$(echo "$PROTOCOLS" | jq -Rsc 'split("\n")[:-1] | {include: map({image_path: .})}')" >> $GITHUB_OUTPUT
else
echo 'protocols_matrix={"include":[]}' >> $GITHUB_OUTPUT
fi
if [[ -n "$CONTENT_CHANGES" ]]; then
echo "content_changes=$(echo "$CONTENT_CHANGES" | jq -Rsc 'split("\n")[:-1] | {include: map({image_path: .})}')" >> $GITHUB_OUTPUT
else
echo 'content_changes={"include":[]}' >> $GITHUB_OUTPUT
fi
if [[ -n "$DOCKERFILE_CHANGES" ]]; then
echo "dockerfile_changes=true" >> $GITHUB_OUTPUT
echo "dockerfile_matrix=$(echo "$DOCKERFILE_CHANGES" | jq -Rsc 'split("\n")[:-1] | {include: map({image_path: .})}')" >> $GITHUB_OUTPUT
else
echo 'dockerfile_changes=false' >> $GITHUB_OUTPUT
echo 'dockerfile_matrix={"include":[]}' >> $GITHUB_OUTPUT
fi
scan-dockerfiles:
needs: [detect-changes]
if: |
github.event_name == 'pull_request' &&
github.event.action != 'closed' &&
contains(needs.detect-changes.outputs.dockerfile_changes, 'true')
runs-on: dev
strategy:
matrix: ${{ fromJson(needs.detect-changes.outputs.dockerfile_matrix) }}
fail-fast: false
permissions:
contents: read
security-events: write
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Trivy Dockerfile scan
id: scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'config'
scan-ref: './${{ matrix.image_path }}/Dockerfile'
hide-progress: false
format: 'table'
severity: 'CRITICAL,HIGH'
exit-code: '0'
- name: Generate human-readable report
if: always()
run: |
set -x
trivy config \
--severity CRITICAL,HIGH \
--format table \
--debug \
./${{ matrix.image_path }}/Dockerfile | tee trivy-dockerfile-results.txt
cat trivy-dockerfile-results.txt
- name: Comment on PR
if: always()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -x
if [ -s trivy-dockerfile-results.txt ]; then
echo "Vulnerabilities found, commenting on PR"
{
echo "### 🔍 Trivy Dockerfile Scan Results for \`${{ matrix.image_path }}/Dockerfile\`"
echo ""
echo "<details>"
echo "<summary>Click to view findings</summary>"
echo ""
echo "\`\`\`"
cat trivy-dockerfile-results.txt
echo "\`\`\`"
echo "</details>"
echo ""
echo "Please review these findings and make necessary fixes to improve the security of your Dockerfile."
} > comment.md
gh pr comment ${{ github.event.pull_request.number }} --body-file comment.md
else
echo "No vulnerabilities found in Dockerfile"
gh pr comment ${{ github.event.pull_request.number }} --body "✅ No security issues found in \`${{ matrix.image_path }}/Dockerfile\`"
fi
check-protocols:
needs: [detect-changes]
if: |
github.event_name == 'pull_request' &&
github.event.action != 'closed' &&
fromJson(needs.detect-changes.outputs.content_changes).include[0]
runs-on: dev
outputs:
protocols_changed: ${{ steps.check-protocol.outputs.protocols_changed }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Check protocol keys in protocols.yaml
id: check-protocol
run: |
# Get all changed protocols from detect-changes
CHANGED_PROTOCOLS='${{ needs.detect-changes.outputs.content_changes }}'
# Check each protocol key
echo "$CHANGED_PROTOCOLS" | jq -r '.[] | .image_path' | while read -r IMAGE_PATH; do
echo "Checking protocol in $IMAGE_PATH"
# Get protocol key from babel.yaml
PROTOCOL_KEY=$(yq e '.protocol_key' "$IMAGE_PATH/babel.yaml")
echo "Checking for protocol key: $PROTOCOL_KEY"
# Check if key exists in protocols.yaml
if ! yq e '.[] | select(.key == "'$PROTOCOL_KEY'")' protocols/protocols.yaml > /dev/null 2>&1; then
echo " Protocol key '$PROTOCOL_KEY' not found in protocols/protocols.yaml"
exit 1
fi
done
# Check if protocols.yaml has changed
if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep -q "^protocols/protocols.yaml$"; then
echo "protocols/protocols.yaml has changed, will push protocol updates"
echo "protocols_changed=true" >> $GITHUB_OUTPUT
else
echo "protocols/protocols.yaml unchanged"
echo "protocols_changed=false" >> $GITHUB_OUTPUT
fi
build-base-images:
name: build-base-images
needs: [detect-changes, scan-dockerfiles]
if: |
github.event_name == 'pull_request' &&
fromJson(needs.detect-changes.outputs.base_matrix).include[0] &&
github.event.action != 'closed' &&
(needs.scan-dockerfiles.result == 'success' || needs.scan-dockerfiles.result == 'skipped')
runs-on: dev
permissions:
contents: read # Allows reading repository contents
packages: write # Allows pushing/pulling container images
security-events: write # For trivy scan results
pull-requests: write # For commenting on PRs
strategy:
matrix: ${{ fromJson(needs.detect-changes.outputs.base_matrix) }}
outputs:
image_name: ${{ steps.version.outputs.image_name }}
image_tag: ${{ steps.version.outputs.image_tag }}
version_tag: ${{ steps.version.outputs.version_tag }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Authenticate GitHub CLI
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token
- name: Extract image name
id: version
run: |
set -x
# Extract image name from path - get the last part of the path
IMAGE_NAME=$(basename ${{ matrix.image_path }})
echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT
# Generate version tag
SHA=$(git rev-parse --short HEAD)
DATE=$(date '+%Y%m%d')
# Try to get the current build number, default to 0 if API call fails
set +e
OUTPUT=$(gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/orgs/blockjoy/packages/container/${IMAGE_NAME}/versions" 2>&1)
API_STATUS=$?
set -e
if echo "$OUTPUT" | grep -q "message.*Package not found" || \
echo "$OUTPUT" | grep -q "status.*404" || \
[ $API_STATUS -ne 0 ]; then
echo "Package $IMAGE_NAME not found in registry - this is expected for new images"
echo "Starting with build number 1"
BUILD_NUM=1
else
echo "API call succeeded, parsing response"
LATEST_BUILD=$(echo "$OUTPUT" | \
jq "[.[] | select(.metadata.container.tags[] | startswith(\"v${DATE}\"))] | length")
if [ -z "$LATEST_BUILD" ] || [ "$LATEST_BUILD" = "null" ]; then
echo "No existing versions found for today, starting at 0"
BUILD_NUM=0
else
echo "Found $LATEST_BUILD existing versions today"
BUILD_NUM=$((LATEST_BUILD + 1))
fi
fi
echo "Debug: Final build number: ${BUILD_NUM}"
# Format: v20250108.1, v20250108.2, etc
VERSION_TAG="v${DATE}.${BUILD_NUM}"
# Make sure we have a build number before continuing
if [ -z "$BUILD_NUM" ]; then
echo "Error: Build number is empty, defaulting to 0"
BUILD_NUM=0
VERSION_TAG="v${DATE}.${BUILD_NUM}"
fi
echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT
echo "image_tag=${SHA}" >> $GITHUB_OUTPUT
echo "version_tag=${VERSION_TAG}" >> $GITHUB_OUTPUT
echo "sha=${SHA}" >> $GITHUB_OUTPUT
- name: Generate build contexts
id: build-contexts
run: |
# First, find all directories containing Dockerfiles to build our valid image list
VALID_IMAGES=$(find . -name Dockerfile -exec dirname {} \; | while read dir; do
# Convert directory path to image name (e.g., ./ethereum/ethereum-erigon -> ethereum-erigon)
basename "$dir" | tr '[:upper:]' '[:lower:]'
# For protocol directories, also add the protocol-client format
if [[ "$dir" =~ ^./[^/]+/[^/]+ ]]; then
echo "$dir" | sed 's|^./\([^/]\+\)/\([^/]\+\)|\1-\2|' | tr '[:upper:]' '[:lower:]'
fi
done | sort -u)
# Now extract FROM directives and filter against our valid images
BUILD_CONTEXTS=$(grep -h "^FROM.*\${.*_IMAGE}" ./${{ matrix.image_path }}/Dockerfile | while read -r line; do
# Extract variable name from FROM line
var_name=$(echo "$line" | grep -o '\${[^}]*}' | tr -d '${}\n')
# Extract image name and remove any default tag
image=$(grep "^ARG ${var_name}=" ./${{ matrix.image_path }}/Dockerfile | cut -d'=' -f2 | cut -d':' -f1)
# Check if this image is in our valid image list
if echo "$VALID_IMAGES" | grep -q "^${image}$"; then
# Use short name for context key but specify it as a docker-image context with SHA tag
echo "${image}=docker-image://ghcr.io/blockjoy/${image}:${{ steps.version.outputs.sha }}"
fi
done | sort -u | tr '\n' ',' | sed 's/,$//')
echo "contexts=${BUILD_CONTEXTS}" >> $GITHUB_OUTPUT
echo "Generated build contexts: ${BUILD_CONTEXTS}"
echo "Valid images: ${VALID_IMAGES}"
echo "Current image path: '${{ matrix.image_path }}'"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3
with:
buildkitd-flags: |
--debug
--allow-insecure-entitlement network.host
- name: Build base image
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6
with:
context: ./${{ matrix.image_path }}
push: false
load: true
build-args: |
GRAFANA_LOKI_API_KEY=${{ secrets.GRAFANA_LOKI_API_KEY }}
GRAFANA_PROM_API_KEY=${{ secrets.GRAFANA_PROM_API_KEY }}
build-contexts: ${{ steps.build-contexts.outputs.contexts }}
cache-from: type=gha
cache-to: type=gha,mode=max
tags: |
localhost/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.image_tag }}
- name: Run Trivy vulnerability scanner
id: scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'image'
image-ref: 'localhost/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.image_tag }}'
format: 'table'
severity: 'CRITICAL,HIGH'
timeout: '10m'
exit-code: '0'
- name: Generate human-readable report
if: always()
run: |
set -x
trivy image --severity CRITICAL,HIGH --format table localhost/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.image_tag }} > trivy-image-results.txt
- name: Comment on PR
if: always()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -x
if [ -s trivy-image-results.txt ]; then
{
echo "### 🔍 Trivy Image Scan Results for \`${{ steps.version.outputs.image_name }}\`"
echo ""
echo "<details><summary>Click to show scan results</summary>"
echo ""
echo "\`\`\`"
cat trivy-image-results.txt
echo "\`\`\`"
echo ""
echo "</details>"
echo ""
echo "These vulnerabilities were found in the ${{ steps.version.outputs.image_name }} image. Since this is a base image, these vulnerabilities will affect all derived images. Please review and update dependencies to their latest secure versions where possible."
} > comment.md
gh pr comment ${{ github.event.pull_request.number }} --body-file comment.md
exit 0
else
echo "No vulnerabilities found in image"
gh pr comment ${{ github.event.pull_request.number }} --body "✅ No vulnerabilities found in \`${{ steps.version.outputs.image_name }}\` image"
fi
- name: Log in to GitHub Container Registry
if: success()
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push base image
if: success()
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6
with:
context: ./${{ matrix.image_path }}
push: true
build-args: |
GRAFANA_LOKI_API_KEY=${{ secrets.GRAFANA_LOKI_API_KEY }}
GRAFANA_PROM_API_KEY=${{ secrets.GRAFANA_PROM_API_KEY }}
build-contexts: ${{ steps.build-contexts.outputs.contexts }}
cache-from: type=gha
cache-to: type=gha,mode=max
tags: |
ghcr.io/blockjoy/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.image_tag }}
ghcr.io/blockjoy/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.version_tag }}
build-clients:
needs: [detect-changes, scan-dockerfiles]
if: |
github.event_name == 'pull_request' &&
fromJson(needs.detect-changes.outputs.clients_matrix).include[0] &&
github.event.action != 'closed' &&
(needs.scan-dockerfiles.result == 'success' || needs.scan-dockerfiles.result == 'skipped')
runs-on: dev
permissions:
contents: read # Allows reading repository contents
packages: write # Allows pushing/pulling container images
strategy:
matrix: ${{ fromJson(needs.detect-changes.outputs.clients_matrix) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Authenticate GitHub CLI
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token
- name: Log in to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate version
id: version
run: |
set -x # Enable debug mode to see commands being executed
set +e
SHA=$(git rev-parse --short HEAD)
DATE=$(date '+%Y%m%d')
CLIENT_NAME=$(basename ${{ matrix.image_path }})
IMAGE_NAME="${CLIENT_NAME}"
# Extract version for any client under /clients/
if [[ "${{ matrix.image_path }}" =~ ^clients/ ]]; then
CLIENT_UPPER=$(echo "${CLIENT_NAME}" | tr '[:lower:]' '[:upper:]')
CLIENT_VERSION=$(grep -E "ENV ${CLIENT_UPPER}_VERSION=[[:space:]]*v?[0-9]+\.[0-9]+\.[0-9]+[-.a-zA-Z0-9]*" "${{ matrix.image_path }}/Dockerfile" | grep -oE "v?[0-9]+\.[0-9]+\.[0-9]+[-.a-zA-Z0-9]*")
if [[ ! -z "$CLIENT_VERSION" ]]; then
# Remove 'v' prefix and ensure version is Docker-compatible (only alphanumeric and . - _)
CLIENT_VERSION=${CLIENT_VERSION#v}
CLIENT_VERSION=$(echo "$CLIENT_VERSION" | tr -cd '[:alnum:].-')
IMAGE_TAG="${CLIENT_VERSION}-${SHA}"
else
IMAGE_TAG="${SHA}"
fi
else
IMAGE_TAG="${SHA}"
fi
echo "Debug: matrix.image_path = ${{ matrix.image_path }}"
echo "Debug: IMAGE_NAME = ${IMAGE_NAME}"
echo "Debug: IMAGE_TAG = ${IMAGE_TAG}"
# Try to get the current build number, default to 0 if API call fails
echo "Debug: Querying GitHub API for package ${IMAGE_NAME}"
OUTPUT=$(gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/orgs/blockjoy/packages/container/${IMAGE_NAME}/versions" 2>&1)
API_STATUS=$?
echo "Debug: Full command output:"
echo "$OUTPUT"
echo "Debug: API Status: $API_STATUS"
echo "Debug: Checking if output contains error message..."
# More detailed error checking
if echo "$OUTPUT" | grep -q "message.*Package not found" || \
echo "$OUTPUT" | grep -q "status.*404" || \
[ $API_STATUS -ne 0 ]; then
echo "Package $IMAGE_NAME not found in registry - this is expected for new images"
echo "Starting with build number 1"
BUILD_NUM=1
else
echo "API call succeeded, parsing response"
LATEST_BUILD=$(echo "$OUTPUT" | \
jq "[.[] | select(.metadata.container.tags[] | startswith(\"v${DATE}\"))] | length")
if [ -z "$LATEST_BUILD" ] || [ "$LATEST_BUILD" = "null" ]; then
echo "No existing versions found for today, starting at 1"
BUILD_NUM=1
else
echo "Found $LATEST_BUILD existing versions today"
BUILD_NUM=$((LATEST_BUILD + 1))
fi
fi
echo "Debug: Final build number: ${BUILD_NUM}"
# Format: v20250108.1, v20250108.2, etc
VERSION_TAG="v${DATE}.${BUILD_NUM}"
echo "Debug: Version tag: ${VERSION_TAG}"
# Make sure we have a build number before continuing
if [ -z "$BUILD_NUM" ]; then
echo "Error: Build number is empty, defaulting to 1"
BUILD_NUM=1
VERSION_TAG="v${DATE}.${BUILD_NUM}"
fi
echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT
echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT
echo "version_tag=${VERSION_TAG}" >> $GITHUB_OUTPUT
echo "sha=${SHA}" >> $GITHUB_OUTPUT
- name: Generate build contexts
id: build-contexts
run: |
# First, find all directories containing Dockerfiles to build our valid image list
VALID_IMAGES=$(find . -name Dockerfile -exec dirname {} \; | while read dir; do
# Convert directory path to image name (e.g., ./ethereum/ethereum-erigon -> ethereum-erigon)
basename "$dir" | tr '[:upper:]' '[:lower:]'
# For protocol directories, also add the protocol-client format
if [[ "$dir" =~ ^./[^/]+/[^/]+ ]]; then
echo "$dir" | sed 's|^./\([^/]\+\)/\([^/]\+\)|\1-\2|' | tr '[:upper:]' '[:lower:]'
fi
done | sort -u)
# Now extract FROM directives and filter against our valid images
BUILD_CONTEXTS=$(grep -h "^FROM.*\${.*_IMAGE}" ./${{ matrix.image_path }}/Dockerfile | while read -r line; do
# Extract variable name from FROM line
var_name=$(echo "$line" | grep -o '\${[^}]*}' | tr -d '${}\n')
# Extract image name and remove any default tag
image=$(grep "^ARG ${var_name}=" ./${{ matrix.image_path }}/Dockerfile | cut -d'=' -f2 | cut -d':' -f1)
# Check if this image is in our valid image list
if echo "$VALID_IMAGES" | grep -q "^${image}$"; then
# Use short name for context key but specify it as a docker-image context with SHA tag
echo "${image}=docker-image://ghcr.io/blockjoy/${image}:${{ steps.version.outputs.sha }}"
fi
done | sort -u | tr '\n' ',' | sed 's/,$//')
echo "contexts=${BUILD_CONTEXTS}" >> $GITHUB_OUTPUT
echo "Generated build contexts: ${BUILD_CONTEXTS}"
echo "Valid images: ${VALID_IMAGES}"
echo "Current image path: '${{ matrix.image_path }}'"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3
with:
buildkitd-flags: |
--debug
--allow-insecure-entitlement network.host
- name: Build and push client images
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6
with:
context: ./${{ matrix.image_path }}
push: true
build-args: |
GRAFANA_LOKI_BASICAUTH=${{ secrets.GRAFANA_LOKI_BASICAUTH }}
GRAFANA_PROM_BASICAUTH=${{ secrets.GRAFANA_PROM_BASICAUTH }}
CLOUDFLARE_API_KEY=${{ secrets.CLOUDFLARE_API_KEY }}
build-contexts: ${{ steps.build-contexts.outputs.contexts }}
cache-from: type=gha
cache-to: type=gha,mode=max
tags: |
ghcr.io/blockjoy/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.image_tag }}
ghcr.io/blockjoy/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.version_tag }}
build-protocols:
needs: [detect-changes, scan-dockerfiles]
environment: Dev
if: |
github.event_name == 'pull_request' &&
fromJson(needs.detect-changes.outputs.protocols_matrix).include[0] &&
github.event.action != 'closed' &&
(needs.scan-dockerfiles.result == 'success' || needs.scan-dockerfiles.result == 'skipped')
runs-on: dev
outputs:
matrix: ${{ needs.detect-changes.outputs.protocols_matrix }}
image_name: ${{ steps.version.outputs.image_name }}
image_tag: ${{ steps.version.outputs.image_tag }}
runner_hostname: ${{ steps.get-hostname.outputs.hostname }}
strategy:
matrix: ${{ fromJson(needs.detect-changes.outputs.protocols_matrix) }}
fail-fast: false
permissions:
contents: read
packages: write
steps:
- name: Get runner hostname
id: get-hostname
run: echo "hostname=$(hostname)" >> $GITHUB_OUTPUT
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Authenticate GitHub CLI
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token
- name: Setup NIB auth
run: |
echo '${{ secrets.NIB_AUTH }}' > ~/.nib.json
- name: Log in to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate version
id: version
run: |
set -x # Enable debug mode to see commands being executed
set +e
SHA=$(git rev-parse --short HEAD)
DATE=$(date '+%Y%m%d')
PROTOCOL_PATH=${{ matrix.image_path }}
# Extract just the protocol name without the parent directory
PROTOCOL=$(echo "${{ matrix.image_path }}" | cut -d'/' -f2)
IMAGE_NAME=$(echo "${{ matrix.image_path }}" | cut -d'/' -f3)
echo "Debug: matrix.image_path = ${{ matrix.image_path }}"
echo "Debug: IMAGE_NAME = ${IMAGE_NAME}"
# Try to get the current build number, default to 0 if API call fails
echo "Debug: Querying GitHub API for package ${IMAGE_NAME}"
OUTPUT=$(gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/orgs/blockjoy/packages/container/${IMAGE_NAME}/versions" 2>&1)
API_STATUS=$?
set -e
# More detailed error checking
if echo "$OUTPUT" | grep -q "message.*Package not found" || \
echo "$OUTPUT" | grep -q "status.*404" || \
[ $API_STATUS -ne 0 ]; then
echo "Package $IMAGE_NAME not found in registry - this is expected for new images"
echo "Starting with build number 1"
BUILD_NUM=1
else
echo "API call succeeded, parsing response"
# Get the highest build number for today's date
LATEST_BUILD=$(echo "$OUTPUT" | \
jq -r "[.[] | select(.metadata.container.tags[] | select(startswith(\"v${DATE}\"))) |
.metadata.container.tags[] | select(startswith(\"v${DATE}\")) |
split(\".\")[1] | tonumber] | max // 0")
if [ -z "$LATEST_BUILD" ] || [ "$LATEST_BUILD" = "null" ]; then
echo "No existing versions found for today, starting at 1"
BUILD_NUM=1
else
echo "Found highest build number $LATEST_BUILD for today"
BUILD_NUM=$((LATEST_BUILD + 1))
fi
fi
echo "Debug: Final build number: ${BUILD_NUM}"
# Format: v20250108.1, v20250108.2, etc
VERSION_TAG="v${DATE}.${BUILD_NUM}"
echo "Debug: Version tag: ${VERSION_TAG}"
# Make sure we have a build number before continuing
if [ -z "$BUILD_NUM" ]; then
echo "Error: Build number is empty, defaulting to 1"
BUILD_NUM=1
VERSION_TAG="v${DATE}.${BUILD_NUM}"
fi
echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT
echo "image_tag=${SHA}" >> $GITHUB_OUTPUT
echo "version_tag=${VERSION_TAG}" >> $GITHUB_OUTPUT
echo "sha=${SHA}" >> $GITHUB_OUTPUT
# Script should succeed even if API call failed
exit 0
- name: Generate build contexts
id: build-contexts
run: |
# First, find all directories containing Dockerfiles to build our valid image list
VALID_IMAGES=$(find . -name Dockerfile -exec dirname {} \; | while read dir; do
# Convert directory path to image name (e.g., ./ethereum/ethereum-erigon -> ethereum-erigon)
basename "$dir" | tr '[:upper:]' '[:lower:]'
# For protocol directories, also add the protocol-client format
if [[ "$dir" =~ ^./[^/]+/[^/]+ ]]; then
echo "$dir" | sed 's|^./\([^/]\+\)/\([^/]\+\)|\1-\2|' | tr '[:upper:]' '[:lower:]'
fi
done | sort -u)
# Now extract FROM directives and filter against our valid images
BUILD_CONTEXTS=$(grep -h "^FROM.*\${.*_IMAGE}" ./${{ matrix.image_path }}/Dockerfile | while read -r line; do
# Extract variable name from FROM line
var_name=$(echo "$line" | grep -o '\${[^}]*}' | tr -d '${}\n')
# Extract image name and remove any default tag
image=$(grep "^ARG ${var_name}=" ./${{ matrix.image_path }}/Dockerfile | cut -d'=' -f2 | cut -d':' -f1)
# Check if this image is in our valid image list
if echo "$VALID_IMAGES" | grep -q "^${image}$"; then
# Use short name for context key but specify it as a docker-image context with SHA tag
echo "${image}=docker-image://ghcr.io/blockjoy/${image}:${{ steps.version.outputs.sha }}"
fi
done | sort -u | tr '\n' ',' | sed 's/,$//')
echo "contexts=${BUILD_CONTEXTS}" >> $GITHUB_OUTPUT
echo "Generated build contexts: ${BUILD_CONTEXTS}"
echo "Valid images: ${VALID_IMAGES}"
echo "Current image path: '${{ matrix.image_path }}'"
- name: Generate build args
id: build-args
run: |
# Extract all *_IMAGE args from Dockerfile with their specified tags
IMAGE_ARGS=$(grep "^ARG.*_IMAGE=" ./${{ matrix.image_path }}/Dockerfile | while read -r line; do
var_name=$(echo "$line" | cut -d'=' -f1 | cut -d' ' -f2)
# Use the full image reference from the Dockerfile, including its tag
image_ref=$(echo "$line" | cut -d'=' -f2)
echo "${var_name}=${image_ref}"
done | tr '\n' '\n')
echo "image_args<<EOF" >> $GITHUB_OUTPUT
echo "$IMAGE_ARGS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3
with:
buildkitd-flags: |
--debug
--allow-insecure-entitlement network.host
- name: Build and push protocol images
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6
with:
context: ./${{ matrix.image_path }}
push: true
build-args: |
GRAFANA_LOKI_BASICAUTH=${{ secrets.GRAFANA_LOKI_BASICAUTH }}
GRAFANA_PROM_BASICAUTH=${{ secrets.GRAFANA_PROM_BASICAUTH }}
CLOUDFLARE_API_KEY=${{ secrets.CLOUDFLARE_API_KEY }}
${{ steps.build-args.outputs.image_args }}
build-contexts: ${{ steps.build-contexts.outputs.contexts }}
cache-from: |
type=registry,ref=ghcr.io/blockjoy/${{ steps.version.outputs.image_name }}:buildcache
cache-to: |
type=registry,ref=ghcr.io/blockjoy/${{ steps.version.outputs.image_name }}:buildcache,mode=max
platforms: linux/amd64
tags: |
ghcr.io/blockjoy/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.image_tag }}
ghcr.io/blockjoy/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.version_tag }}
- name: Get variants from babel.yaml
id: get-variants
run: |
variants=$(yq e '.variants[].key' ${{ matrix.image_path }}/babel.yaml | tr '\n' ' ')
if [ -z "$variants" ]; then
echo " No variants found in ${{ matrix.image_path }}/babel.yaml"
exit 1
fi
echo "variants=$variants" >> $GITHUB_OUTPUT
- name: Check variants
id: check-variants
run: |
mkdir -p ~/temp
chmod 777 ~/temp
export TMPDIR=~/temp
echo "TMPDIR=~/temp" >> $GITHUB_ENV
echo "APPTAINER_TMPDIR=~/temp" >> $GITHUB_ENV
cp ${{ matrix.image_path }}/babel.yaml ${{ matrix.image_path }}/babel.yaml-test
# Update container_uri with newly built image
NEW_URI="docker://ghcr.io/blockjoy/${{ steps.version.outputs.image_name }}:${{ steps.version.outputs.image_tag }}"
TMPDIR=~/temp yq -i e ".container_uri = \"$NEW_URI\"" ${{ matrix.image_path }}/babel.yaml-test
echo "test_file=${{ matrix.image_path }}/babel.yaml-test" >> $GITHUB_OUTPUT
success_variants=()
failed_variants=()
for variant in ${{ steps.get-variants.outputs.variants }}; do
echo "Checking variant: $variant"
echo "Running: nib image check --lint-only --variant $variant --path ${{ matrix.image_path }}/babel.yaml-test"
if OUTPUT=$(nib image check --lint-only --variant $variant --path ${{ matrix.image_path }}/babel.yaml-test 2>&1); then
success_variants+=("$variant")
echo " Variant $variant check passed"
echo "Command output:"
echo "$OUTPUT"
else
echo " Variant $variant check failed"
echo "Command output:"
echo "$OUTPUT"
failed_variants+=("$variant")
fi
done
rm -rf ${{ matrix.image_path }}/babel.yaml-test
if [ ${#failed_variants[@]} -gt 0 ]; then
echo " The following variants failed checks: ${failed_variants[*]}"
exit 1
fi
echo "success_variants=${success_variants[*]}" >> $GITHUB_OUTPUT
get-host:
runs-on: dev
outputs:
target_host: ${{ steps.get-host.outputs.target_host }}
steps:
- name: Get previous host
id: get-host
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PREV_HOST=$(gh pr view ${{ github.event.pull_request.number }} --json comments --jq '
.comments
| map(select(.author.login == "github-actions"))
| map(select(.body | test("All nodes have been cleaned up")))
| sort_by(.createdAt)
| last
| if . == null then "" else
(.body | capture("The following nodes have been deployed on `(?<host>[^`]+)`:\\n").host)
end
')
if [ -z "$PREV_HOST" ]; then
echo "No previous host found, using default"
PREV_HOST="dev"
else
echo "Found previous host: $PREV_HOST"
fi
echo "target_host=$PREV_HOST" >> $GITHUB_OUTPUT
cleanup:
needs: [get-host]
runs-on: ${{ needs.get-host.outputs.target_host }}
steps:
- name: Cleanup nodes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Finding nodes to clean up..."
# Find the latest cleanup comment timestamp
LAST_CLEANUP=$(gh pr view ${{ github.event.pull_request.number }} --json comments --jq '
.comments
| map(select(.author.login == "github-actions"))
| map(select(.body | test("All nodes have been cleaned up")))
| sort_by(.createdAt)
| last
| if . == null then "" else .createdAt end
')
echo "Last cleanup was at: $LAST_CLEANUP"
# Get list of nodes from PR comments after last cleanup
NODES=$(gh pr view ${{ github.event.pull_request.number }} --json comments | jq -r --arg last "$LAST_CLEANUP" '
.comments
| map(select(.author.login == "github-actions"))
| map(select(.body | contains("following nodes have been deployed")))
| map(select(
if $last == "" then true
else (.createdAt > $last)
end
))
| map(.body | split("\n")
| map(select(contains("running variant")))
| map(capture("- `(?<n>[a-z-]+-[a-z-]+-[a-z-]+)`, running variant `[^`]+`").n)
| join("\n")
)
| join("\n")
')
if [ ! -z "$NODES" ]; then
echo "Found nodes to clean up:"
echo "$NODES"
CLEANED_NODES=0
while read -r node; do
if [ -z "$node" ]; then
echo "Skipping empty node name"
continue
fi
echo "Processing node: '$node'"
if bv n delete -y "$node"; then
CLEANED_NODES=$((CLEANED_NODES + 1))
else
echo "Warning: Failed to delete node '$node'"
fi
done <<< "$NODES"
# Only post message if we actually cleaned up nodes
if [ $CLEANED_NODES -gt 0 ]; then
if [[ "${{ github.event.pull_request.merged }}" == "true" ]]; then
gh pr comment ${{ github.event.pull_request.number }} --body " All nodes have been cleaned up after merge"
elif [[ "${{ github.event.action }}" == "closed" ]]; then
gh pr comment ${{ github.event.pull_request.number }} --body " All nodes have been cleaned up after PR close"
elif [[ "${{ github.event.action }}" == "synchronize" ]]; then
gh pr comment ${{ github.event.pull_request.number }} --body " All nodes have been cleaned up after PR update"
else
gh pr comment ${{ github.event.pull_request.number }} --body " All nodes have been cleaned up"
fi
fi
else
echo "No nodes found to clean up, exiting cleanly"
fi
test-nodes:
needs: [build-protocols, get-host, cleanup]
if: |
github.event_name == 'pull_request' &&
github.event.action != 'closed'
runs-on: ${{ needs.get-host.outputs.target_host }}
environment: Dev
strategy:
matrix: ${{ fromJson(needs.build-protocols.outputs.matrix) }}
outputs:
runner_host: ${{ steps.test-nodes.outputs.runner_host }}
node_names: ${{ steps.test-nodes.outputs.node_names }}
permissions:
contents: read
packages: write
statuses: write
pull-requests: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Authenticate GitHub CLI
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token
- name: Setup NIB auth
run: |
echo '${{ secrets.NIB_AUTH }}' > ~/.nib.json
- name: Create test babel file
run: |
# Copy babel.yaml to test file
cp ${{ matrix.image_path }}/babel.yaml ${{ matrix.image_path }}/babel.yaml-test
# Extract image name the same way as build-protocols
IMAGE_NAME=$(echo "${{ matrix.image_path }}" | cut -d'/' -f3)
# Update container_uri with newly built image
NEW_URI="docker://ghcr.io/blockjoy/${IMAGE_NAME}:${{ needs.build-protocols.outputs.image_tag }}"
TMPDIR=~/temp yq -i e ".container_uri = \"$NEW_URI\"" ${{ matrix.image_path }}/babel.yaml-test
- name: Test nodes
id: test-nodes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Store runner hostname
RUNNER_HOST=$(hostname)
echo "runner_host=${RUNNER_HOST}" >> $GITHUB_OUTPUT
# Get variants from babel.yaml
VARIANTS=$(yq e '.variants[].key' ${{ matrix.image_path }}/babel.yaml-test)
if [ $? -ne 0 ]; then
echo "Error: Failed to get variants from babel.yaml"
echo "Content of babel.yaml-test:"
cat ${{ matrix.image_path }}/babel.yaml-test
exit 1
fi
# Initialize arrays for node info
declare -a NODE_NAMES=()
declare -a DEPLOYED_NODES=()
declare -i CHECK_FAILED=0
START_FAILED=false
# For each variant, start a node and capture info
for variant in $VARIANTS; do
echo "Testing variant: $variant"
# Run nib image check and capture output
echo "Running: nib image check --variant $variant --path ${{ matrix.image_path }}/babel.yaml-test"
OUTPUT=$(nib image check --variant $variant --path ${{ matrix.image_path }}/babel.yaml-test 2>&1 || true)
NIB_EXIT=$?
echo "nib image check output:"
echo "$OUTPUT"
# Check if node was started regardless of final exit code
if echo "$OUTPUT" | grep -q "Node started"; then
# Extract node name from the 'Created ... dev_node' line
NODE_NAME=$(echo "$OUTPUT" | grep -o "'[^']*'" | head -1 | tr -d "'")
if [ -z "$NODE_NAME" ]; then
echo "Error: Failed to extract node name from output"
((CHECK_FAILED++))
continue
fi
# Store for later use
NODE_NAMES+=("$NODE_NAME")
# Add to deployed nodes list before attempting start
DEPLOYED_NODES+=("- \`${NODE_NAME}\`, running variant \`${variant}\`")
# Start the node
echo "Starting node ${NODE_NAME}"
if ! START_OUTPUT=$(bv n start "$NODE_NAME" 2>&1); then
echo "Error: Failed to start node ${NODE_NAME}:"
echo "$START_OUTPUT"
((CHECK_FAILED++))
else
echo "Successfully started node ${NODE_NAME}"
fi
fi
if [ $NIB_EXIT -ne 0 ]; then
echo "Error: nib image check failed with exit code $NIB_EXIT"
((CHECK_FAILED++))
fi
done
# Post comment about all deployed nodes
if [ ${#DEPLOYED_NODES[@]} -gt 0 ]; then
COMMENT="The following nodes have been deployed on \`${RUNNER_HOST}\`:"$'\n'
for node in "${DEPLOYED_NODES[@]}"; do
COMMENT="${COMMENT}${node}"$'\n'
done
COMMENT="${COMMENT}"$'\n'"These nodes will be cleaned up on PR merge or close and replaced on PR update."
echo -e "$COMMENT" | gh pr comment ${{ github.event.pull_request.number }} --body-file -
fi
# Save node names for cleanup
echo "node_names=${NODE_NAMES[*]}" >> $GITHUB_OUTPUT
echo "start_failed=${START_FAILED}" >> $GITHUB_OUTPUT
# Exit with failure if any checks failed
if [ $CHECK_FAILED -gt 0 ]; then
echo "Error: $CHECK_FAILED node checks/starts failed"
exit 1
fi
echo "status=success" >> $GITHUB_OUTPUT
exit 0
workflow-status:
needs: [detect-changes, test-nodes, build-base-images, build-clients, build-protocols, scan-dockerfiles]
if: always() && github.event_name == 'pull_request' && github.event.action != 'closed'
runs-on: dev
permissions:
statuses: write
steps:
- name: Report Status
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ "${{ needs.build-base-images.result }}" == "failure" ]] || \
[[ "${{ needs.build-clients.result }}" == "failure" ]] || \
[[ "${{ needs.build-protocols.result }}" == "failure" ]] || \
[[ "${{ needs.test-nodes.result }}" == "failure" ]] || \
[[ "${{ needs.scan-dockerfiles.result }}" == "failure" ]]; then
DESCRIPTION="Workflow failed"
[[ "${{ needs.test-nodes.result }}" == "failure" ]] && DESCRIPTION="Node testing failed"
[[ "${{ needs.scan-dockerfiles.result }}" == "failure" ]] && DESCRIPTION="Dockerfile scanning failed"
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
"/repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \
-f state='failure' \
-f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
-f description="$DESCRIPTION" \
-f context="workflow-status"
exit 1
fi
# All jobs either succeeded or were skipped
if [[ "${{ needs.detect-changes.outputs.content_changes }}" == '{"include":[]}' ]]; then
DESCRIPTION="No protocol changes detected that require testing"
else
DESCRIPTION="All checks passed successfully"
fi
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
"/repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \
-f state='success' \
-f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
-f description="$DESCRIPTION" \
-f context="workflow-status"
push-protocols:
environment: Dev
needs: [check-protocols]
if: needs.check-protocols.outputs.protocols_changed == 'true'
runs-on: dev
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup NIB auth
run: |
echo '${{ secrets.NIB_AUTH }}' > ~/.nib.json
- name: Push protocols to dev
run: |
echo "Pushing protocol updates to dev"
nib protocol push --path protocols/protocols.yaml