Skip to content
Merged
Show file tree
Hide file tree
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
111 changes: 110 additions & 1 deletion scripts/build_with_cache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,93 @@ build_inputs_hash() {
rm -f "$file_list" "$hash_list"
}

artifact_tree_hash() {
local artifact_root="$1"
local file_list
local hash_input
local rel
local path
local target

[ -d "$artifact_root" ] || return 1
file_list=$(mktemp)
hash_input=$(mktemp)

if ! (cd "$artifact_root" && find . -mindepth 1 -print | LC_ALL=C sort) > "$file_list"; then
rm -f "$file_list" "$hash_input"
return 1
fi

while IFS= read -r rel; do
[ -n "$rel" ] || continue
path="${artifact_root}/${rel#./}"
if [ -d "$path" ] && [ ! -L "$path" ]; then
printf 'D %s\n' "$(bk_base64_encode_value "$rel")" >> "$hash_input"
elif [ -f "$path" ] && [ ! -L "$path" ]; then
printf 'F %s %s\n' "$(bk_sha256_file "$path")" "$(bk_base64_encode_value "$rel")" >> "$hash_input"
elif [ -L "$path" ]; then
if ! target=$(readlink "$path"); then
rm -f "$file_list" "$hash_input"
return 1
fi
printf 'L %s %s\n' "$(bk_base64_encode_value "$target")" "$(bk_base64_encode_value "$rel")" >> "$hash_input"
else
echo "unsupported artifact entry type: ${rel}" >&2
rm -f "$file_list" "$hash_input"
return 1
fi
done < "$file_list"

bk_sha256_file "$hash_input"
rm -f "$file_list" "$hash_input"
}

validate_payload_integrity() {
local artifacts_dir="$1"
local source_info_file="$2"
local label="$3"
local expected_source_info_sha256
local current_source_info_sha256
local expected_artifacts_sha256
local current_artifacts_sha256

expected_source_info_sha256=$(manifest_value BK_CACHE_SOURCE_INFO_SHA256)
if [ -z "$expected_source_info_sha256" ]; then
echo "cache manifest is missing source_info.env digest"
return 1
fi
if [ ! -f "$source_info_file" ]; then
echo "${label} source_info.env is missing"
return 1
fi
if ! current_source_info_sha256=$(bk_sha256_file "$source_info_file"); then
echo "cannot hash ${label} source_info.env"
return 1
fi
if [ "$current_source_info_sha256" != "$expected_source_info_sha256" ]; then
echo "${label} source_info.env digest changed"
return 1
fi

expected_artifacts_sha256=$(manifest_value BK_CACHE_ARTIFACTS_SHA256)
if [ -z "$expected_artifacts_sha256" ]; then
echo "cache manifest is missing artifact digest"
return 1
fi
if [ ! -d "$artifacts_dir" ]; then
echo "${label} artifacts are missing"
return 1
fi
if ! current_artifacts_sha256=$(artifact_tree_hash "$artifacts_dir"); then
echo "cannot hash ${label} artifacts"
return 1
fi
if [ "$current_artifacts_sha256" != "$expected_artifacts_sha256" ]; then
echo "${label} artifact digest changed"
return 1
fi
}

host_environment_fingerprint() {
local snapshot_file="${repo_root}/results/environment_snapshot_build_actual.json"
local fingerprint_input
Expand Down Expand Up @@ -369,6 +456,11 @@ restore_cache() {
return 1
fi

if ! reason=$(validate_payload_integrity "${cache_dir}/artifacts" "$cached_source_info" "cached"); then
write_status miss "$reason"
return 1
fi

if ! reason=$(validate_cached_source "$cached_source_info"); then
write_status miss "$reason"
return 1
Expand All @@ -380,6 +472,12 @@ restore_cache() {
if [ -d "${cache_dir}/results" ]; then
cp -a "${cache_dir}/results/." "${repo_root}/results/"
fi
if ! reason=$(validate_payload_integrity "${repo_root}/artifacts" "${repo_root}/results/source_info.env" "restored"); then
rm -rf "${repo_root}/artifacts"
rm -f "${repo_root}/results/source_info.env"
write_status miss "$reason"
return 1
fi
write_status hit "restored cached build artifacts"
echo "build cache: hit for ${code}/${system}"
}
Expand All @@ -389,6 +487,7 @@ store_cache() {
local inputs_hash
local source_info_sha256=""
local source_info_file="${repo_root}/results/source_info.env"
local artifacts_sha256=""
local container_sha256=""
local host_fingerprint=""

Expand All @@ -414,7 +513,6 @@ store_cache() {
fi

inputs_hash=$(build_inputs_hash)
source_info_sha256=$(bk_sha256_file "$source_info_file")
tmp_dir="${cache_dir}.tmp.$$"
rm -rf "$tmp_dir"
mkdir -p "${tmp_dir}/artifacts" "${tmp_dir}/results"
Expand All @@ -424,12 +522,23 @@ store_cache() {
cp "${repo_root}/results/environment_snapshot_build_actual.json" \
"${tmp_dir}/results/environment_snapshot_build_actual.json"
fi
if ! source_info_sha256=$(bk_sha256_file "${tmp_dir}/results/source_info.env"); then
rm -rf "$tmp_dir"
write_status miss "build completed but source_info.env cannot be hashed"
return 0
fi
if ! artifacts_sha256=$(artifact_tree_hash "${tmp_dir}/artifacts"); then
rm -rf "$tmp_dir"
write_status miss "build completed but artifacts cannot be hashed"
return 0
fi
{
printf 'BK_BUILD_CACHE_FORMAT=base64-v1\n'
printf 'BK_CACHE_CODE_B64=%s\n' "$(bk_base64_encode_value "$code")"
printf 'BK_CACHE_SYSTEM_B64=%s\n' "$(bk_base64_encode_value "$system")"
printf 'BK_CACHE_BUILD_INPUTS_SHA256=%s\n' "$inputs_hash"
printf 'BK_CACHE_SOURCE_INFO_SHA256=%s\n' "$source_info_sha256"
printf 'BK_CACHE_ARTIFACTS_SHA256=%s\n' "$artifacts_sha256"
printf 'BK_CACHE_HOST_ENV_FINGERPRINT=%s\n' "$host_fingerprint"
printf 'BK_CACHE_ENV_KEY_B64=%s\n' "$(bk_base64_encode_value "${BK_BUILD_CACHE_ENV_KEY:-}")"
printf 'BK_CACHE_CREATED_AT=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
Expand Down
36 changes: 36 additions & 0 deletions scripts/tests/test_build_cache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,18 @@ run_build_with_cache() {
run_build_with_cache_for_root "${TMP_DIR}/project"
}

run_integrity_build_with_cache() {
pushd "${TMP_DIR}/project" >/dev/null
BK_BENCHKIT_ROOT="${TMP_DIR}/project" \
BK_BUILD_CACHE_DIR="${TMP_DIR}/integrity-cache" \
BK_BUILD_CACHE_ALLOW_HOST_ENV_CACHE=true \
BK_BUILD_CACHE_ENV_KEY=test-toolchain-v1 \
BK_TEST_SOURCE_REPO="${TMP_DIR}/source/.git" \
BK_TEST_BUILD_COUNT="${TMP_DIR}/integrity-build-count" \
bash scripts/build_with_cache.sh app IntegritySystem programs/app
popd >/dev/null
}

run_build_without_host_restore_opt_in_for_root() {
local project_root="$1"

Expand Down Expand Up @@ -253,6 +265,30 @@ grep -q "$app_build_hash" "${TMP_DIR}/cache/app/TestSystem/manifest.env" && {
exit 1
}

rm -rf "${TMP_DIR}/project/artifacts" "${TMP_DIR}/project/results" "${TMP_DIR}/project/appsrc"
run_integrity_build_with_cache
test "$(cat "${TMP_DIR}/integrity-build-count")" = "1"
integrity_cache_dir="${TMP_DIR}/integrity-cache/app/IntegritySystem"
awk -F= '$1 == "BK_CACHE_SOURCE_INFO_SHA256" && length($2) == 64 {found=1} END {exit(found ? 0 : 1)}' \
"${integrity_cache_dir}/manifest.env"
awk -F= '$1 == "BK_CACHE_ARTIFACTS_SHA256" && length($2) == 64 {found=1} END {exit(found ? 0 : 1)}' \
"${integrity_cache_dir}/manifest.env"

printf 'tampered artifact\n' > "${integrity_cache_dir}/artifacts/app.bin"
rm -rf "${TMP_DIR}/project/artifacts" "${TMP_DIR}/project/results" "${TMP_DIR}/project/appsrc"
run_integrity_build_with_cache
test "$(cat "${TMP_DIR}/integrity-build-count")" = "2"
grep -q "$first_commit" "${TMP_DIR}/project/artifacts/app.bin"
grep -q '^BK_BUILD_CACHE_STORED=true$' "${TMP_DIR}/project/results/build_cache.env"

printf '\n# tampered source info\n' >> "${integrity_cache_dir}/results/source_info.env"
rm -rf "${TMP_DIR}/project/artifacts" "${TMP_DIR}/project/results" "${TMP_DIR}/project/appsrc"
run_integrity_build_with_cache
test "$(cat "${TMP_DIR}/integrity-build-count")" = "3"
grep -q "$first_commit" "${TMP_DIR}/project/artifacts/app.bin"
grep -q '^BK_BUILD_CACHE_STORED=true$' "${TMP_DIR}/project/results/build_cache.env"

rm -rf "${TMP_DIR}/project/artifacts" "${TMP_DIR}/project/results" "${TMP_DIR}/project/appsrc"
cp -a "${TMP_DIR}/project" "${TMP_DIR}/project-copy"
rm -rf "${TMP_DIR}/project-copy/artifacts" "${TMP_DIR}/project-copy/results" "${TMP_DIR}/project-copy/appsrc"
run_build_with_cache_for_root "${TMP_DIR}/project-copy"
Expand Down
Loading