Skip to content
Open
Changes from all commits
Commits
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
241 changes: 241 additions & 0 deletions hack/ubi-bump-prs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#!/usr/bin/env bash
# Copyright The Conforma Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

#
# Create UBI base image bump PRs for all active release branches.
#
# Usage:
# hack/ubi-bump-prs.sh # all default branches
# hack/ubi-bump-prs.sh main # specific branch(es)
# hack/ubi-bump-prs.sh release-v0.8 main # multiple specific branches
#
# The script is attended — it pauses for confirmation before creating each PR.
#

set -o errexit
set -o nounset
set -o pipefail

# --- Configuration -----------------------------------------------------------

if [[ $# -gt 0 ]]; then
BRANCHES=("$@")
else

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] indentation

The script uses hard tab indentation throughout, while all other hack/ shell scripts consistently use 2-space indentation. Verified against ubi-base-image-bump.sh, cut-release.sh, go-mod-upgrade-helper.sh, derive-version.sh, add-auto-tag.sh, bump-tekton-bundles.sh, and update-rpm-lock.sh.

Suggested fix: Convert tab indentation to 2-space indentation to match the established convention.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] command-injection

Branch names from command-line arguments flow into git show, git checkout -B, git push, and gh pr create --base commands. Mitigated by proper quoting of $BRANCH in all uses and git's naming constraints; further reduced by the attended, interactive nature of the script.

BRANCHES=(release-v0.7 release-v0.8 main)
fi

UPSTREAM_REMOTE=upstream
PUSH_REMOTE=origin

UBI_MINIMAL_REPO=registry.access.redhat.com/ubi9/ubi-minimal
UBI_MINIMAL="${UBI_MINIMAL_REPO}:latest"

DOCKER_FILES=(Dockerfile Dockerfile.dist acceptance/kubernetes/kind/acceptance.Dockerfile)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] maintenance

The DOCKER_FILES array duplicates the identical array in hack/ubi-base-image-bump.sh (line 33). If the inner script's list changes, the outer script's git add would miss newly-modified Dockerfiles, resulting in partial commits.

Suggested fix: Consider using git add -u to add all modified tracked files, or sourcing the list from a shared location.

# --- Prerequisites ------------------------------------------------------------

for cmd in skopeo podman gh; do
if ! command -v "$cmd" &>/dev/null; then
echo "Error: $cmd is required but not found."
exit 1
fi
done

if ! git diff --quiet || ! git diff --cached --quiet; then
echo "Error: working tree has uncommitted changes. Commit or stash first."
exit 1
fi

# --- Setup --------------------------------------------------------------------

ORIGINAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
cleanup() { git checkout "$ORIGINAL_BRANCH" 2>/dev/null || true; }
trap cleanup EXIT

echo "=== Fetching $UPSTREAM_REMOTE ==="
git fetch "$UPSTREAM_REMOTE"
echo

echo "=== Checking latest $UBI_MINIMAL digest ==="
Comment thread
simonbaird marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] edge-case

LATEST_DIGEST is computed once at setup via skopeo inspect --raw | sha256sum, but the inner hack/ubi-base-image-bump.sh runs its own independent skopeo inspect. If the :latest tag moves between calls, the initial printout may differ from the actual digest written. Cosmetic only — commit messages and PR bodies correctly use NEW_DIGEST from the Dockerfile.

LATEST_DIGEST=$(skopeo inspect --raw docker://"$UBI_MINIMAL" | sha256sum | awk '{print $1}')
echo "Latest: sha256:${LATEST_DIGEST:0:16}..."
echo

CREATED_PRS=()

# --- Per-branch loop ----------------------------------------------------------

for BRANCH in "${BRANCHES[@]}"; do
echo "============================================"
echo " $BRANCH"
echo "============================================"

if ! OLD_DIGEST=$(git show "$UPSTREAM_REMOTE/$BRANCH:Dockerfile" 2>/dev/null \
| sed -nE 's/.*ubi-minimal:latest@sha256:([0-9a-f]{64}).*/\1/p' | head -1) || [[ -z "$OLD_DIGEST" ]]; then
echo "Could not extract current digest for $BRANCH, skipping."
echo
continue
fi

if [[ "$OLD_DIGEST" == "$LATEST_DIGEST" ]]; then
echo "Already up to date on $BRANCH, skipping."
echo
continue
fi

echo "Old digest: sha256:${OLD_DIGEST:0:16}..."
echo "New digest: sha256:${LATEST_DIGEST:0:16}..."
echo

# Checkout working branch
Comment thread
simonbaird marked this conversation as resolved.
WORK_BRANCH="ubi-bump-${BRANCH}"
git checkout -B "$WORK_BRANCH" "$UPSTREAM_REMOTE/$BRANCH" --no-track

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] edge-case

When NEW_DIGEST extraction fails after the bump script has already modified files (line 100: hack/ubi-base-image-bump.sh --no-commit), the continue on line 106 skips to the next branch iteration without cleaning up the dirty working tree. The next iteration's git checkout -B will fail because git refuses to overwrite uncommitted modifications. With set -o errexit, this kills the script, and the cleanup trap's git checkout may also fail, leaving the repository in a dirty state on the wrong branch.

Suggested fix: Add git reset --hard "$UPSTREAM_REMOTE/$BRANCH" before the continue on line 106, matching the reset done on the user-skip path (line 137).

echo

# Run the existing bump script (no commit)
hack/ubi-base-image-bump.sh --no-commit

# Verify the new digest (use whatever the bump script actually wrote)
NEW_DIGEST=$(sed -nE 's/.*ubi-minimal:latest@sha256:([0-9a-f]{64}).*/\1/p' Dockerfile | head -1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] command-injection

Containers run via podman run --rm without explicit security hardening flags. Digest values are regex-constrained to [0-9a-f]{64}, preventing injection. Matches the existing hack/update-rpm-lock.sh pattern.

if [[ -z "$NEW_DIGEST" ]]; then
echo "Error: could not extract ubi-minimal digest from Dockerfile after bump"
continue
fi
echo
echo "Digest after bump: sha256:${NEW_DIGEST:0:16}..."

# Generate RPM diff (will use the native arch of whoever runs this script)
echo
echo "Pulling images for RPM comparison..."
OLD_RPMS=$(podman run --rm "${UBI_MINIMAL_REPO}@sha256:$OLD_DIGEST" rpm -qa | sort)
NEW_RPMS=$(podman run --rm "${UBI_MINIMAL_REPO}@sha256:$NEW_DIGEST" rpm -qa | sort)

RPM_DIFF=$(diff \
--old-line-format='- %L' \
--new-line-format='+ %L' \
--unchanged-line-format='' \
<(echo "$OLD_RPMS") <(echo "$NEW_RPMS") || true)

echo
if [[ -n "$RPM_DIFF" ]]; then
echo "RPM changes:"
echo "$RPM_DIFF"
else
echo "No RPM changes detected."
fi

echo
echo "File changes:"
git diff --stat
echo

# --- Prompt ---------------------------------------------------------------

read -rp ">>> Create PR for $BRANCH? [y/N] " answer
echo
case "$answer" in
[yY]) ;;
*)
echo "Skipping $BRANCH."
git reset --hard "$UPSTREAM_REMOTE/$BRANCH"
echo
continue
;;
esac

# --- Commit ---------------------------------------------------------------

COMMIT_MSG="chore(deps): Update ubi-minimal base image

Old digest: sha256:$OLD_DIGEST
New digest: sha256:$NEW_DIGEST"

if [[ -n "$RPM_DIFF" ]]; then
COMMIT_MSG="$COMMIT_MSG

RPM changes:

$RPM_DIFF"
fi

EXISTING_FILES=()
for f in "${DOCKER_FILES[@]}" rpms.lock.yaml; do
[[ -f "$f" ]] && EXISTING_FILES+=("$f")
done
git add "${EXISTING_FILES[@]}"
git commit -m "$COMMIT_MSG"

# --- Push -----------------------------------------------------------------

if ! git push -u "$PUSH_REMOTE" "$WORK_BRANCH" 2>&1; then
echo
echo "Push failed — remote branch may already exist."
git fetch "$PUSH_REMOTE" "$WORK_BRANCH"
read -rp ">>> Retry with --force-with-lease? [y/N] " force_answer
case "$force_answer" in
[yY])
git push --force-with-lease -u "$PUSH_REMOTE" "$WORK_BRANCH"
;;
*)
echo "Skipping PR for $BRANCH."
echo
continue
;;
esac
fi

# --- Create PR ------------------------------------------------------------

PR_BODY="Update ubi-minimal base image to latest digest.

Old digest: \`sha256:$OLD_DIGEST\`
New digest: \`sha256:$NEW_DIGEST\`"

if [[ -n "$RPM_DIFF" ]]; then
PR_BODY="$PR_BODY

### RPM changes

\`\`\`diff
$RPM_DIFF
\`\`\`"
fi

PR_URL=$(gh pr create \
--base "$BRANCH" \
--title "chore(deps): Update ubi-minimal base image (${BRANCH#release-})" \
--body "$PR_BODY")

CREATED_PRS+=("$BRANCH: $PR_URL")
echo "Created: $PR_URL"
echo
done

# --- Summary ------------------------------------------------------------------

echo
echo "============================================"
echo " Summary"
echo "============================================"
if [[ ${#CREATED_PRS[@]} -gt 0 ]]; then
for pr in "${CREATED_PRS[@]}"; do
echo " $pr"
done
else
echo " No PRs created."
fi
echo
Loading