Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7dc9040
chore: update next builds to be hermetic
Zaperex Nov 19, 2025
88d59a7
chore: migrate to use downstream dockerfile as default
Zaperex Nov 19, 2025
dd0ff4b
chore: update symlinks for aarch64
Zaperex Nov 20, 2025
b41ec95
chore: refactor docker build setup
Zaperex Nov 21, 2025
818535c
Merge branch 'main' of github.com:redhat-developer/rhdh into add-mult…
Zaperex Nov 21, 2025
6ee8187
chore: update the update-Dockerfile.sh script with updated containerf…
Zaperex Nov 21, 2025
25e4ce1
chore: update docs and scripts with new container file location
Zaperex Nov 21, 2025
d6f5590
chore: remove dead janus slack invite link
Zaperex Nov 21, 2025
7eacea9
chore: update pr podman push with new artifact name
Zaperex Nov 21, 2025
f444c66
chore: add missing architecture field in artifact names
Zaperex Nov 21, 2025
de9ea21
chore: remove arch in artifacts since we don't do multi-arch for PRs
Zaperex Nov 21, 2025
608e3de
Merge branch 'main' into add-multi-arch-hermetic-builds
Zaperex Nov 24, 2025
1fac820
Merge branch 'main' of github.com:redhat-developer/rhdh into add-mult…
Zaperex Nov 24, 2025
36b2d84
refactor: enhance GitHub Actions workflows with improved environment …
Zaperex Nov 24, 2025
9f5e9ba
chore: update workflows to use default github envs
Zaperex Nov 26, 2025
0569022
Merge branch 'main' of github.com:redhat-developer/rhdh into add-mult…
Zaperex Nov 26, 2025
a2f9309
Merge branch 'main' of github.com:redhat-developer/rhdh into add-mult…
Zaperex Nov 28, 2025
ab56193
Merge branch 'main' of github.com:redhat-developer/rhdh into add-mult…
Zaperex Nov 28, 2025
f66d8a3
chore: add cursor rule to enforce github actions best practices
Zaperex Nov 28, 2025
cb8873a
Merge branch 'main' of github.com:redhat-developer/rhdh into add-mult…
Zaperex Dec 2, 2025
2d1c2b3
chore: remove unnecessary comment
Zaperex Dec 2, 2025
9b35634
Merge branch 'main' of github.com:redhat-developer/rhdh into add-mult…
Zaperex Dec 5, 2025
53017fa
chore: refactor local hermeto install script
Zaperex Dec 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
607 changes: 607 additions & 0 deletions .claude/memories/github-workflows-security.md

Large diffs are not rendered by default.

613 changes: 613 additions & 0 deletions .cursor/rules/github-workflows-security.mdc

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,12 @@ examples
.ibm/images/*
!.ibm/images/Dockerfile
!.yarnrc.yml
hermeto-cache
hermeto-cache
.github
.cursor
.claude
.rulesync
.rulesyncignore
.pr_agents.toml
.sonarcloud.properties

15 changes: 9 additions & 6 deletions .github/actions/check-author/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,28 @@ runs:
shell: bash
env:
GH_TOKEN: ${{ inputs.gh_token }}
AUTHOR: ${{ inputs.author }}
TEAM: ${{ inputs.team }}
ORGANIZATION: ${{ inputs.organization }}
run: |
echo "Checking if ${{ inputs.author }} is an active member of ${{ inputs.team }} team in ${{ inputs.organization }}..."
echo "Checking if $AUTHOR is an active member of $TEAM team in $ORGANIZATION..."

# Use the memberships endpoint to get specific membership info
if response=$(gh api "/orgs/${{ inputs.organization }}/teams/${{ inputs.team }}/memberships/${{ inputs.author }}" 2>/dev/null); then
if response=$(gh api "/orgs/${ORGANIZATION}/teams/${TEAM}/memberships/${AUTHOR}" 2>/dev/null); then
state=$(echo "$response" | jq -r '.state')

if [[ "$state" == "active" ]]; then
echo "✓ Author ${{ inputs.author }} is an active member of the ${{ inputs.team }} team"
echo "✓ Author $AUTHOR is an active member of the $TEAM team"
echo "is_active_member=true" >> $GITHUB_OUTPUT
elif [[ "$state" == "pending" ]]; then
echo "⚠️ Author ${{ inputs.author }} has a pending invitation to the ${{ inputs.team }} team (not active)"
echo "⚠️ Author $AUTHOR has a pending invitation to the $TEAM team (not active)"
echo "is_active_member=false" >> $GITHUB_OUTPUT
else
echo "✗ Author ${{ inputs.author }} has unexpected membership state: $state"
echo "✗ Author $AUTHOR has unexpected membership state: $state"
echo "is_active_member=false" >> $GITHUB_OUTPUT
fi
else
# API call failed (user not in team)
echo "✗ Author ${{ inputs.author }} is not a member of the ${{ inputs.team }} team"
echo "✗ Author $AUTHOR is not a member of the $TEAM team"
echo "is_active_member=false" >> $GITHUB_OUTPUT
fi
29 changes: 17 additions & 12 deletions .github/actions/check-image-and-changes/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ runs:
- name: Check for relevant changes
id: changes
shell: bash
env:
GET_SHA_OUTPUT: ${{ steps.get-sha.outputs.short_sha }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
# Ensure that SHORT_SHA is available for subsequent steps in this context
echo "SHORT_SHA=${SHORT_SHA:-${{ steps.get-sha.outputs.short_sha }}}" >> $GITHUB_ENV
echo "SHORT_SHA=${SHORT_SHA:-$GET_SHA_OUTPUT}" >> $GITHUB_ENV

# Get base commit for comparison
BASE_COMMIT=$(git merge-base ${{ github.event.pull_request.base.sha }} HEAD)
BASE_COMMIT=$(git merge-base "$PR_BASE_SHA" HEAD)
echo "BASE_COMMIT=$BASE_COMMIT"

# Get list of changed files
Expand All @@ -53,8 +56,7 @@ runs:
while IFS= read -r file; do
if [[ -n "$file" ]]; then
# If file is NOT in one of the non-essential directories, mark as relevant
# contents of .rhdh/docker are only for downstream and we do zero testing of those python or docker files in upstream e2e
if ! [[ "$file" =~ ^(e2e|e2e-tests|doc|docs|\.ibm|\.rhdh/docker)/ ]]; then
if ! [[ "$file" =~ ^(e2e|e2e-tests|doc|docs|\.ibm)/ ]]; then
echo "Relevant change detected: $file"
RELEVANT_CHANGES=true
break
Expand All @@ -77,16 +79,19 @@ runs:
- name: Check if Docker image exists
id: image-check
shell: bash
env:
PR_NUMBER: ${{ github.event.number }}
IMAGE_REGISTRY: ${{ env.REGISTRY }}
run: |
# First check the PR base tag (most important for skipping builds)
IMAGE_TAG_PR="pr-${{ github.event.number }}"
IMAGE_NAME_PR="${{ env.REGISTRY }}/rhdh-community/rhdh:${IMAGE_TAG_PR}"
IMAGE_TAG_PR="pr-${PR_NUMBER}"
IMAGE_NAME_PR="${IMAGE_REGISTRY}/rhdh-community/rhdh:${IMAGE_TAG_PR}"

IMAGE_EXISTS_PR=$(curl -s "https://quay.io/api/v1/repository/rhdh-community/rhdh/tag/" | jq -r --arg tag "$IMAGE_TAG_PR" '.tags[] | select(.name == $tag) | .name')

# For safety, also check commit-specific tag
IMAGE_TAG_COMMIT="pr-${{ github.event.number }}-${SHORT_SHA}"
IMAGE_NAME_COMMIT="${{ env.REGISTRY }}/rhdh-community/rhdh:${IMAGE_TAG_COMMIT}"
IMAGE_TAG_COMMIT="pr-${PR_NUMBER}-${SHORT_SHA}"
IMAGE_NAME_COMMIT="${IMAGE_REGISTRY}/rhdh-community/rhdh:${IMAGE_TAG_COMMIT}"

IMAGE_EXISTS_COMMIT=$(curl -s "https://quay.io/api/v1/repository/rhdh-community/rhdh/tag/" | jq -r --arg tag "$IMAGE_TAG_COMMIT" '.tags[] | select(.name == $tag) | .name')

Expand All @@ -105,12 +110,12 @@ runs:
- name: Determine final skip decision
id: final-decision
shell: bash
env:
SKIP_BUILD_TAG: ${{ steps.changes.outputs.skip_build_tag }}
RELEVANT_CHANGES: ${{ steps.changes.outputs.relevant_changes }}
IMAGE_EXISTS: ${{ steps.image-check.outputs.exists }}
run: |
# Get the values from previous steps
SKIP_BUILD_TAG="${{ steps.changes.outputs.skip_build_tag }}"
RELEVANT_CHANGES="${{ steps.changes.outputs.relevant_changes }}"
IMAGE_EXISTS="${{ steps.image-check.outputs.exists }}"

echo "Skip build tag: $SKIP_BUILD_TAG"
echo "Relevant changes: $RELEVANT_CHANGES"
echo "Image exists: $IMAGE_EXISTS"
Expand Down
140 changes: 49 additions & 91 deletions .github/actions/docker-build/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,48 +15,31 @@
name: Docker Build
description: Docker Build
inputs:
registry:
description: The registry to push to
required: true
password:
description: The password to use for the registry
required: false
username:
description: The username to use for the registry
required: false
imageName:
description: The name of the image to build
description: The full image name including registry (e.g., quay.io/rhdh-community/rhdh)
required: true
imageTags:
description: The tags to apply to the image
required: true
imageLabels:
description: The labels for the Docker image
required: false
push:
description: Whether to push the image (automatically ignored and assumed to be false if enableHermeticBuild is true)
required: true
platform:
description: "Target given CPU platform architecture (default: linux/amd64)"
required: false
default: linux/amd64
enableHermeticBuild:
description: Whether to enable hermetic builds using hermeto (currently only supported for linux/amd64)
required: false
default: 'false'
componentDirectory:
description: Path to the component directory for hermetic builds
required: false
default: '.'
dockerfilePath:
description: Path to the Dockerfile to use
containerfilePath:
description: Path to the Containerfile to use
required: false
default: 'docker/Dockerfile'

outputs:
digest:
description: The digest of the built Docker image
value: ${{ steps.build.outputs.digest }}
default: 'docker/Containerfile'
skipArtifactUpload:
description: Skip uploading the built image as a GitHub artifact
required: false
default: 'false'

runs:
using: composite
Expand All @@ -72,81 +55,67 @@ runs:
docker-images: false
swap-storage: false

- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3

# - name: Install qemu dependency
# shell: bash
# run: |
# set -ex
# sudo apt-get update
# sudo apt-get install -y qemu-user-static

- name: Login to Registry
if: ${{ inputs.push == 'true' && inputs.enableHermeticBuild != 'true' }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ${{ inputs.registry }}
username: ${{ inputs.username }}
password: ${{ inputs.password }}

- name: Extract metadata (tags, labels, annotations) for Docker
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: ${{ inputs.registry }}/${{ inputs.imageName }}
images: ${{ inputs.imageName }}
tags: |
${{ inputs.imageTags }}
labels: |
${{ inputs.imageLabels }}

# Hermetic Build Steps
- name: Set up hermetic build variables
if: ${{ inputs.enableHermeticBuild == 'true' }}
shell: bash
env:
COMPONENT_DIR: ${{ inputs.componentDirectory }}
run: |
echo "HERMETO_IMAGE=quay.io/konflux-ci/hermeto:latest" >> $GITHUB_ENV
echo "LOCAL_CACHE_DIR=./hermeto-cache/$(basename ${{ inputs.componentDirectory }})" >> $GITHUB_ENV
echo "COMPONENT_ABS_DIR=${{ github.workspace }}/${{ inputs.componentDirectory }}" >> $GITHUB_ENV
echo "LOCAL_CACHE_DIR=./hermeto-cache/$(basename "$COMPONENT_DIR")" >> $GITHUB_ENV
echo "COMPONENT_ABS_DIR=${GITHUB_WORKSPACE}/${COMPONENT_DIR}" >> $GITHUB_ENV

- name: Cache dependencies with hermeto
if: ${{ inputs.enableHermeticBuild == 'true' }}
shell: bash
run: |
set -ex

echo "=== Creating local cache directory ==="
echo "::group::Creating local cache directory"
mkdir -p ${{ env.LOCAL_CACHE_DIR }} || echo "Failed to create local cache directory"
echo "::endgroup::"

echo "=== Fetching dependencies with hermeto ==="
# Build hermeto cache for rpm, yarn, and pip (currently does not support ARM64 due to quay.io/konflux-ci/hermeto:latest not having an arm64 image)
echo "::group::Fetching dependencies with hermeto"
# Build hermeto cache for rpm, yarn, and pip
podman run --rm -v "$PWD:/source:z" -v "$LOCAL_CACHE_DIR:/cachi2:z" -w /source "$HERMETO_IMAGE" \
--log-level DEBUG \
fetch-deps --dev-package-managers \
--source . \
--output /cachi2/output \
'[{"type": "rpm", "path": "."}, {"type": "yarn","path": "."}, {"type": "yarn","path": "./dynamic-plugins"}, {"type": "pip","path": "./python", "allow_binary": "false"}]' || echo "Fetch-deps failed"
echo "::endgroup::"

if [ -d ${{ env.LOCAL_CACHE_DIR }}/output ]; then
echo "=== Output directory exists, running generate-env ==="
echo "::group::Generating environment file"

# Generate environment file
podman run --rm -v "$PWD:/source:z" -v "$LOCAL_CACHE_DIR:/cachi2:z" -w /source "$HERMETO_IMAGE" \
--log-level DEBUG \
generate-env --format env \
--output /cachi2/cachi2.env /cachi2/output
echo "::endgroup::"

else
echo "No output directory found, skipping generate-env"
exit 1
fi

if [ -d ${{ env.LOCAL_CACHE_DIR }}/output ]; then
echo "=== Running inject-files ==="
echo "::group::Injecting files"

podman run --rm -v "$PWD:/source:z" -v "$LOCAL_CACHE_DIR:/cachi2:z" -w /source "$HERMETO_IMAGE" \
--log-level DEBUG \
inject-files /cachi2/output || echo "Inject-files failed"
echo "::endgroup::"

else
echo "No output directory found, skipping inject-files"
Expand All @@ -156,59 +125,51 @@ runs:
echo LOCAL_CACHE_DIR_REALPATH=$(realpath "${{ env.LOCAL_CACHE_DIR }}") >> $GITHUB_ENV

- name: "Fix Cache Ownership for Non-Root Buildah"
if: ${{ inputs.enableHermeticBuild == 'true' }}
shell: bash
run: |
set -ex
echo "::group::Fixing cache ownership"

echo "=== Before ownership fix ==="
ls -l ${{ env.LOCAL_CACHE_DIR_REALPATH }}

echo "=== Attempting to fix ownership to runner user ==="
sudo chown -R runner ${{ env.LOCAL_CACHE_DIR_REALPATH }}

echo "=== After ownership fix ==="
ls -l ${{ env.LOCAL_CACHE_DIR_REALPATH }}

echo "::endgroup::"

- name: Transform Containerfile for hermetic build
if: ${{ inputs.enableHermeticBuild == 'true' }}
shell: bash
id: transform-containerfile
env:
CONTAINERFILE_PATH: ${{ inputs.containerfilePath }}
TRANSFORMED_CONTAINERFILE: ${{ inputs.containerfilePath }}.hermeto
run: |
set -x

CONTAINERFILE_PATH="${{ inputs.dockerfilePath }}"

TRANSFORMED_CONTAINERFILE="${CONTAINERFILE_PATH}.hermeto"
echo "::group::Transforming Containerfile for hermetic build"

# Copy original dockerfile for hermetic build modifications
cp "$CONTAINERFILE_PATH" "$TRANSFORMED_CONTAINERFILE"

# Transform the dockerfile to simulate Konflux build
# Configure dnf to use the cachi2 repo
sed -i '/RUN *\(dnf\|microdnf\) install/i RUN rm -r /etc/yum.repos.d/* && cp /cachi2/output/deps/rpm/x86_64/repos.d/hermeto.repo /etc/yum.repos.d/' "$TRANSFORMED_CONTAINERFILE"
# Configure dnf to use the cachi2 repo (supports both x86_64 and aarch64)
sed -i '/RUN *\(dnf\|microdnf\) install/i RUN rm -r /etc/yum.repos.d/* && cp /cachi2/output/deps/rpm/$(uname -m)/repos.d/hermeto.repo /etc/yum.repos.d/' "$TRANSFORMED_CONTAINERFILE"

# Inject the cachi2 env variables to every RUN command
sed -i 's/^\s*RUN /RUN . \/cachi2\/cachi2.env \&\& /' "$TRANSFORMED_CONTAINERFILE"
echo "::endgroup::"

echo "TRANSFORMED_CONTAINERFILE=$TRANSFORMED_CONTAINERFILE" >> $GITHUB_ENV
echo "transformed_containerfile=$TRANSFORMED_CONTAINERFILE" >> $GITHUB_OUTPUT

- name: Build and push Docker image (Standard)
if: ${{ inputs.enableHermeticBuild != 'true' }}
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
- name: "Build Docker Image"
id: build
with:
context: .
file: ${{ inputs.dockerfilePath }}
push: ${{ inputs.push }}
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
platforms: ${{ inputs.platform }}

- name: "Build Docker Image (Hermetic)"
id: hermetic-build
if: ${{ inputs.enableHermeticBuild == 'true' }}
uses: redhat-actions/buildah-build@7a95fa7ee0f02d552a32753e7414641a04307056 # v2.13
with:
containerfiles: ${{ inputs.dockerfilePath }}.hermeto
containerfiles: ${{ steps.transform-containerfile.outputs.transformed_containerfile }}
context: .
platform: ${{ inputs.platform }}
tags: ${{ steps.meta.outputs.tags }}
Expand All @@ -217,20 +178,16 @@ runs:
--network=none
--volume ${{ env.LOCAL_CACHE_DIR_REALPATH }}:/cachi2:z

- name: Set build output for hermetic builds
if: ${{ inputs.enableHermeticBuild == 'true' }}
- name: Save image as artifact
if: ${{ inputs.skipArtifactUpload != 'true' }}
shell: bash
run: |
echo "digest=${{ steps.hermetic-build.outputs.digest || 'no-digest-available' }}" >> $GITHUB_OUTPUT
env:
TAGS_LIST: ${{ steps.meta.outputs.tags }} # Extract the built image tags from the metadata

- name: Save image as artifact (Hermetic)
if: ${{ inputs.enableHermeticBuild == 'true' }}
shell: bash
run: |
echo "::group::Saving image and metadata for artifact"
mkdir -p ./rhdh-podman-artifacts

# Extract the built image tags from the metadata
TAGS_LIST="${{ steps.meta.outputs.tags }}"


# Save all the built images to tar (podman save can handle multiple tags)
echo "Saving images with tags:"
Expand All @@ -240,12 +197,13 @@ runs:

# Save metadata for the push workflow
echo "$TAGS_LIST" > ./rhdh-podman-artifacts/tags.txt
echo "::endgroup::"

- name: Upload image artifact
if: ${{ inputs.enableHermeticBuild == 'true' }}
if: ${{ inputs.skipArtifactUpload != 'true' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: podman-image-${{ github.event.number || 'main' }}-${{ env.SHORT_SHA }}
name: podman-image-${{ github.event.number || github.ref_name }}-${{ env.SHORT_SHA }}
path: ./rhdh-podman-artifacts/
retention-days: 1
if-no-files-found: error
if-no-files-found: error
Loading
Loading