blob: 9243ed43452677eeaf7229c2cc1828679c987d8b [file]
#!/bin/bash
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
#
#
# Guards the "public/**" shade exclusions added for the Hudi Timeline UI (RFC-94).
# The Timeline UI assets (vis-timeline, Bootstrap, renderjson) must ship ONLY in
# hudi-timeline-server-bundle. The five engine bundles exclude "public/**" so that
# the bundled-library LICENSE obligations stay scoped to the server bundle. RFC-94
# calls this negative assertion load-bearing: without it the exclusion can regress
# silently and an engine bundle would ship UI assets whose licenses are not carried
# there.
#
# Usage: validate_bundle_ui_assets.sh [dir]
# dir Optional directory to search for built bundle jars.
# Defaults to the repo "packaging" directory (scans packaging/*/target/).
#
# CI: invoked by the validate-bundles* jobs in .github/workflows/bot.yml right after the bundle build.
#
set -u
# Resolve the search root.
scriptDir="$(cd "$(dirname "$0")" && pwd)"
repoRoot="$(cd "$scriptDir/../.." && pwd)"
searchDir="${1:-$repoRoot/packaging}"
if [ ! -d "$searchDir" ]; then
echo "Search directory does not exist: $searchDir [ERROR]"
exit 1
fi
# List the entries inside a jar. Prefer "jar", fall back to "unzip -l".
listJar() {
local jar="$1"
if command -v jar >/dev/null 2>&1; then
jar tf "$jar"
elif command -v unzip >/dev/null 2>&1; then
unzip -l "$jar" | awk 'NR>3 {print $4}'
else
echo "__NO_TOOL__"
fi
}
# Find bundle jars matching a name pattern, skipping the unshaded original-*.jar
# and the sources/javadoc/tests attachments.
findBundleJars() {
local pattern="$1"
find "$searchDir" -type f -name "$pattern" \
! -name 'original-*.jar' \
! -name '*-sources.jar' \
! -name '*-javadoc.jar' \
! -name '*-tests.jar' 2>/dev/null
}
violations=0
# --- Positive assertion: the server bundle MUST carry the UI assets ---
serverPattern='hudi-timeline-server-bundle-*.jar'
serverJars=$(findBundleJars "$serverPattern")
if [ -z "$serverJars" ]; then
echo "WARNING: no $serverPattern found under $searchDir; the positive assertion did not run"
else
while IFS= read -r jar; do
[ -z "$jar" ] && continue
entries=$(listJar "$jar")
if [ "$entries" = "__NO_TOOL__" ]; then
echo "Neither 'jar' nor 'unzip' is available to inspect jars [ERROR]"
exit 1
fi
hasIndex=1
hasLib=1
echo "$entries" | grep -q '^public/index.html$' || hasIndex=0
echo "$entries" | grep -q '^public/lib/' || hasLib=0
if [ "$hasIndex" -eq 0 ]; then
echo "MISSING public/index.html in $jar [ERROR]"
violations=$((violations + 1))
fi
if [ "$hasLib" -eq 0 ]; then
echo "MISSING public/lib/ entries in $jar [ERROR]"
violations=$((violations + 1))
fi
if [ "$hasIndex" -eq 1 ] && [ "$hasLib" -eq 1 ]; then
echo "OK: $jar carries the Timeline UI assets"
fi
done <<EOF
$serverJars
EOF
fi
# --- Negative assertion: engine bundles must NOT carry any UI assets ---
# One pattern per bundle so it stays obvious which bundle maps to which expectation.
# Spark and utilities jar names carry a scala suffix (hudi-spark3.5-bundle_2.12-*.jar,
# hudi-utilities-bundle_2.12-*.jar), so their patterns must not assume "-bundle-".
enginePatterns=(
'hudi-spark*-bundle*.jar'
'hudi-flink*-bundle-*.jar'
'hudi-utilities-bundle_*.jar'
'hudi-kafka-connect-bundle-*.jar'
'hudi-integ-test-bundle-*.jar'
)
for pattern in "${enginePatterns[@]}"; do
jars=$(findBundleJars "$pattern")
if [ -z "$jars" ]; then
echo "SKIP: no $pattern found under $searchDir (not built)"
continue
fi
while IFS= read -r jar; do
[ -z "$jar" ] && continue
entries=$(listJar "$jar")
if [ "$entries" = "__NO_TOOL__" ]; then
echo "Neither 'jar' nor 'unzip' is available to inspect jars [ERROR]"
exit 1
fi
# Guard against a vacuous pass: a truncated or unreadable jar yields an empty
# listing, which trivially contains no public/ entries.
if ! echo "$entries" | grep -q '^META-INF/'; then
echo "EMPTY or unreadable jar listing (no META-INF/ entry) for $jar [ERROR]"
violations=$((violations + 1))
continue
fi
offending=$(echo "$entries" | grep '^public/')
if [ -n "$offending" ]; then
echo "FORBIDDEN public/ entries found in $jar [ERROR]:"
echo "$offending" | sed 's/^/ /'
violations=$((violations + 1))
else
echo "OK: $jar carries no public/ assets"
fi
done <<EOF
$jars
EOF
done
if [ "$violations" -ne 0 ]; then
echo ""
echo "Bundle UI asset validation FAILED with $violations violation(s) [ERROR]"
exit 1
fi
echo ""
echo "Bundle UI asset validation passed [OK]"
exit 0