feat(release): add reproducible signed artifacts (#19)
quality-gate / quality (push) Failing after 1m50s
quality-gate / container (push) Has been skipped

This commit is contained in:
KyuubiYoru
2026-07-16 17:48:21 +02:00
parent 07004cd75f
commit cc5793f935
52 changed files with 2568 additions and 73 deletions
+253
View File
@@ -0,0 +1,253 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
version="${1:-}"
output="${2:-}"
property() {
sed -n "s:.*<$1>\(.*\)</$1>.*:\1:p" "$root/eng/Versions.props"
}
if [[ -z "$version" ]]; then
version="$(property RendezvousVersion)"
fi
semver='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-([0-9A-Za-z-]+\.)*[0-9A-Za-z-]+)?(\+([0-9A-Za-z-]+\.)*[0-9A-Za-z-]+)?$'
if [[ ! "$version" =~ $semver ]]; then
echo "Release version is not valid SemVer: $version" >&2
exit 1
fi
prerelease="${version%%+*}"
if [[ "$prerelease" == *-* ]]; then
prerelease="${prerelease#*-}"
IFS='.' read -r -a prerelease_identifiers <<<"$prerelease"
for identifier in "${prerelease_identifiers[@]}"; do
if [[ "$identifier" =~ ^[0-9]+$ && ! "$identifier" =~ ^(0|[1-9][0-9]*)$ ]]; then
echo "Numeric prerelease identifiers must not contain leading zeroes: $version" >&2
exit 1
fi
done
fi
if [[ "$version" != "$(property RendezvousVersion)" ]]; then
echo "Requested version $version differs from eng/Versions.props." >&2
exit 1
fi
for command in dotnet git python3 tar gzip sha256sum cmp jq; do
command -v "$command" >/dev/null || {
echo "Required release command is unavailable: $command" >&2
exit 1
}
done
commit="$(git -C "$root" rev-parse HEAD)"
source_date_epoch="$(git -C "$root" show -s --format=%ct "$commit")"
tree_state=clean
if [[ -n "$(git -C "$root" status --porcelain --untracked-files=normal)" ]]; then
tree_state=dirty
fi
allow_dirty=()
if [[ "$tree_state" != clean ]]; then
if [[ "${RENDEZVOUS_RELEASE_ALLOW_DIRTY:-0}" != 1 ]]; then
echo "A formal release must be built from a clean Git tree." >&2
exit 1
fi
allow_dirty=(--allow-dirty)
fi
if [[ -z "$output" ]]; then
output="$root/artifacts/release/$version"
fi
if [[ -e "$output" ]]; then
echo "Release output already exists; refusing to overwrite: $output" >&2
exit 1
fi
mkdir -p "$(dirname "$output")"
output="$(cd "$(dirname "$output")" && pwd)/$(basename "$output")"
work="$(mktemp -d "${TMPDIR:-/tmp}/rendezvous-release.XXXXXX")"
cleanup() {
rm -rf "$work"
}
trap cleanup EXIT
mkdir -p "$work/pack-1" "$work/pack-2" "$work/publish-1" "$work/publish-2" "$output"
create_server_archive() {
local publish_directory="$1"
local destination="$2"
tar --sort=name \
--mtime="@$source_date_epoch" \
--owner=0 --group=0 --numeric-owner \
-C "$publish_directory" -cf - . \
| gzip -n >"$destination"
}
common=(
-p:ContinuousIntegrationBuild=true
-p:PackageVersion="$version"
-p:RepositoryCommit="$commit"
-p:SourceRevisionId="$commit"
)
cd "$root"
dotnet restore Rendezvous.slnx --locked-mode
python3 eng/release_artifacts.py policy --root "$root" >"$work/dependency-policy.json"
dotnet package list \
--project Rendezvous.slnx \
--vulnerable \
--include-transitive \
--no-restore \
--format json >"$work/nuget-vulnerabilities.json"
python3 eng/release_artifacts.py audit --input "$work/nuget-vulnerabilities.json"
dotnet format Rendezvous.slnx --verify-no-changes --no-restore
api_before="$(sha256sum docs/api/*.json)"
dotnet build Rendezvous.slnx --configuration Release --no-restore "${common[@]}"
cp src/FinalFactory.Rendezvous.Server/bin/Release/net10.0/FinalFactory.Rendezvous.Server.dll \
"$work/FinalFactory.Rendezvous.Server.first.dll"
cp src/FinalFactory.Rendezvous.Server/bin/Release/net10.0/FinalFactory.Rendezvous.Server.pdb \
"$work/FinalFactory.Rendezvous.Server.first.pdb"
dotnet publish src/FinalFactory.Rendezvous.Server/FinalFactory.Rendezvous.Server.csproj \
--configuration Release \
--no-build \
--no-restore \
--output "$work/publish-1" \
-p:UseAppHost=false \
-p:OpenApiGenerateDocuments=false \
"${common[@]}"
create_server_archive "$work/publish-1" "$work/FinalFactory.Rendezvous.Server.first.tar.gz"
if [[ "$tree_state" == clean ]]; then
git diff --exit-code -- docs/api
elif [[ "$api_before" != "$(sha256sum docs/api/*.json)" ]]; then
echo "Generated OpenAPI changed during the release build." >&2
exit 1
fi
dotnet test Rendezvous.slnx --configuration Release --no-build
for project in Client Contracts; do
dotnet pack "src/FinalFactory.Rendezvous.$project/FinalFactory.Rendezvous.$project.csproj" \
--configuration Release --no-build --output "$work/pack-1" "${common[@]}"
done
for package in "$work/pack-1"/*; do
python3 eng/release_artifacts.py normalize-package \
--package "$package" \
--source-date-epoch "$source_date_epoch" \
--version "$version"
done
# Rebuild from the locked graph and prove package byte reproducibility.
dotnet clean Rendezvous.slnx --configuration Release >/dev/null
dotnet restore Rendezvous.slnx --locked-mode
dotnet build Rendezvous.slnx --configuration Release --no-restore "${common[@]}"
for project in Client Contracts; do
python3 eng/release_artifacts.py source-link \
--file "src/FinalFactory.Rendezvous.$project/obj/Release/netstandard2.1/FinalFactory.Rendezvous.$project.sourcelink.json" \
--commit "$commit"
done
cmp --silent \
"$work/FinalFactory.Rendezvous.Server.first.dll" \
src/FinalFactory.Rendezvous.Server/bin/Release/net10.0/FinalFactory.Rendezvous.Server.dll || {
echo "Server assembly is not byte reproducible." >&2
exit 1
}
cmp --silent \
"$work/FinalFactory.Rendezvous.Server.first.pdb" \
src/FinalFactory.Rendezvous.Server/bin/Release/net10.0/FinalFactory.Rendezvous.Server.pdb || {
echo "Server portable PDB is not byte reproducible." >&2
exit 1
}
for project in Client Contracts; do
dotnet pack "src/FinalFactory.Rendezvous.$project/FinalFactory.Rendezvous.$project.csproj" \
--configuration Release --no-build --output "$work/pack-2" "${common[@]}"
done
for package in "$work/pack-2"/*; do
python3 eng/release_artifacts.py normalize-package \
--package "$package" \
--source-date-epoch "$source_date_epoch" \
--version "$version"
done
for package in "$work/pack-1"/*; do
cmp --silent "$package" "$work/pack-2/$(basename "$package")" || {
echo "Package is not byte reproducible: $(basename "$package")" >&2
exit 1
}
done
cp "$work/pack-1"/* "$output/"
python3 eng/release_artifacts.py consumer-config \
--local-source "$output" \
--output "$work/consumer.NuGet.config"
for consumer in spacegame unscouted; do
project="$root/tests/consumers/$consumer/$(find "$root/tests/consumers/$consumer" -maxdepth 1 -name '*.csproj' -printf '%f\n')"
packages="$work/consumer-packages-$consumer"
dotnet restore "$project" \
-p:RendezvousPackageVersion="$version" \
-p:RestoreLockedMode=false \
--packages "$packages" \
--configfile "$work/consumer.NuGet.config" \
--force-evaluate
dotnet build "$project" \
--configuration Release --no-restore \
-p:RendezvousPackageVersion="$version"
assets="$(dirname "$project")/obj/project.assets.json"
python3 eng/release_artifacts.py consumer --assets "$assets" --version "$version"
done
dotnet publish src/FinalFactory.Rendezvous.Server/FinalFactory.Rendezvous.Server.csproj \
--configuration Release \
--no-build \
--no-restore \
--output "$work/publish-2" \
-p:UseAppHost=false \
-p:OpenApiGenerateDocuments=false \
"${common[@]}"
server_archive="$output/FinalFactory.Rendezvous.Server.$version.linux-x64.tar.gz"
create_server_archive "$work/publish-2" "$server_archive"
cmp --silent "$work/FinalFactory.Rendezvous.Server.first.tar.gz" "$server_archive" || {
echo "Server archive is not byte reproducible." >&2
exit 1
}
cp CHANGELOG.md "$output/CHANGELOG.md"
python3 eng/release_artifacts.py sbom \
--root "$root" \
--version "$version" \
--commit "$commit" \
--source-date-epoch "$source_date_epoch" \
--server-deps "$work/publish-2/FinalFactory.Rendezvous.Server.deps.json" \
--output "$output/FinalFactory.Rendezvous.$version.spdx.json"
provenance=(
provenance
--root "$root"
--version "$version"
--commit "$commit"
--tree-state "$tree_state"
--source-date-epoch "$source_date_epoch"
--dotnet-sdk "$(dotnet --version)"
--build-tool "python=$(python3 --version 2>&1)"
--build-tool "tar=$(tar --version | sed -n '1p')"
--build-tool "gzip=$(gzip --version | sed -n '1p')"
--build-tool "jq=$(jq --version)"
--output "$output/release-provenance.json"
)
while IFS= read -r base; do
provenance+=(--base-digest "$base")
done < <(sed -n 's/^FROM \([^ ]*\).*/\1/p' Dockerfile)
while IFS= read -r base; do
provenance+=(--builder-base "$base")
done < <(sed -n 's/^FROM \([^ ]*\).*/\1/p' eng/release-builder.Dockerfile)
python3 eng/release_artifacts.py "${provenance[@]}"
(
cd "$output"
find . -maxdepth 1 -type f ! -name checksums.sha256 -printf '%f\n' \
| LC_ALL=C sort \
| xargs sha256sum >checksums.sha256
)
python3 eng/release_artifacts.py verify \
--root "$root" \
--release-dir "$output" \
--version "$version" \
"${allow_dirty[@]}"
echo "Release artifacts verified at $output"
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
base="${1:-origin/main}"
if ! git -C "$root" cat-file -e "$base^{commit}" 2>/dev/null; then
echo "Compatibility base does not exist; this is valid only for the initial version baseline: $base"
exit 0
fi
if [[ "$(git -C "$root" rev-parse "$base")" == "$(git -C "$root" rev-parse HEAD)" ]]; then
base="HEAD^"
fi
if ! git -C "$root" cat-file -e "$base:eng/Versions.props" 2>/dev/null; then
echo "Base has no release version manifest; accepting the initial compatibility baseline."
exit 0
fi
current_property() {
sed -n "s:.*<$1>\(.*\)</$1>.*:\1:p" "$root/eng/Versions.props"
}
base_property() {
git -C "$root" show "$base:eng/Versions.props" \
| sed -n "s:.*<$1>\(.*\)</$1>.*:\1:p"
}
changed() {
git -C "$root" diff --name-only "$base"...HEAD -- "$@" | grep -q .
}
require_increase() {
local property="$1"
local description="$2"
shift 2
if changed "$@"; then
local before after
before="$(base_property "$property")"
after="$(current_property "$property")"
if [[ ! "$before" =~ ^[0-9]+$ || ! "$after" =~ ^[0-9]+$ || "$after" -le "$before" ]]; then
echo "$description changed without increasing $property ($before -> $after)." >&2
exit 1
fi
fi
}
require_increase RendezvousMajorVersion "Published .NET API snapshot" \
'tests/FinalFactory.Rendezvous.Tests/TestData/Contracts/v*/client-public-api.txt' \
'tests/FinalFactory.Rendezvous.Tests/TestData/Contracts/v*/contracts-public-api.txt'
require_increase HttpContractVersion "HTTP/OpenAPI contract evidence" \
'docs/api/*.json' \
'tests/FinalFactory.Rendezvous.Tests/TestData/Contracts/v*/*.json' \
':(exclude)tests/FinalFactory.Rendezvous.Tests/TestData/Contracts/v*/connection-ticket.json'
require_increase UdpContractVersion "UDP contract evidence" \
'tests/FinalFactory.Rendezvous.Tests/TestData/Contracts/v*/*.hex'
require_increase ConnectionTicketFormatVersion "Connection-ticket format evidence" \
'tests/FinalFactory.Rendezvous.Tests/TestData/Contracts/v*/connection-ticket.json'
echo "Compatibility changes are paired with the required version increase."
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
tag="${1:-${GITHUB_REF_NAME:-}}"
version="$(sed -n 's:.*<RendezvousVersion>\(.*\)</RendezvousVersion>.*:\1:p' "$root/eng/Versions.props")"
if [[ "$tag" != "v$version" ]]; then
echo "Release tag $tag does not match central version v$version." >&2
exit 1
fi
if [[ -n "$(git -C "$root" status --porcelain --untracked-files=normal)" ]]; then
echo "Release tag checkout is not clean." >&2
exit 1
fi
if [[ "$(git -C "$root" tag --points-at HEAD --list "$tag")" != "$tag" ]]; then
echo "Release tag $tag does not point at the checked-out commit." >&2
exit 1
fi
if ! grep -Eq "^## $version - [0-9]{4}-[0-9]{2}-[0-9]{2}$" "$root/CHANGELOG.md"; then
echo "CHANGELOG.md must contain a dated heading for $version." >&2
exit 1
fi
if grep -Eq "^## $version - Unreleased$" "$root/CHANGELOG.md"; then
echo "Release $version is still marked Unreleased." >&2
exit 1
fi
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
version="${1:?usage: finalize-release-candidate.sh VERSION RELEASE_DIRECTORY}"
release_dir="${2:?usage: finalize-release-candidate.sh VERSION RELEASE_DIRECTORY}"
container_sbom="$release_dir/FinalFactory.Rendezvous.Container.$version.spdx.json"
[[ -s "$container_sbom" ]] || {
echo "Container SBOM is missing or empty: $container_sbom" >&2
exit 1
}
(
cd "$release_dir"
find . -maxdepth 1 -type f ! -name checksums.sha256 -printf '%f\n' \
| LC_ALL=C sort \
| xargs sha256sum >checksums.sha256
)
python3 "$root/eng/release_artifacts.py" verify \
--root "$root" \
--release-dir "$release_dir" \
--version "$version" \
--phase publish-ready
echo "Finalized publish-ready release candidate $version"
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
version="${1:?usage: finalize-signing-ready-release.sh VERSION RELEASE_DIRECTORY}"
release_dir="${2:?usage: finalize-signing-ready-release.sh VERSION RELEASE_DIRECTORY}"
(
cd "$release_dir"
find . -maxdepth 1 -type f \
! -name checksums.sha256 \
! -name checksums.sha256.bundle \
-printf '%f\n' \
| LC_ALL=C sort \
| xargs sha256sum >checksums.sha256
)
python3 "$root/eng/release_artifacts.py" verify \
--root "$root" \
--release-dir "$release_dir" \
--version "$version" \
--phase signing-ready
echo "Finalized signing-ready release $version"
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
version="${1:?usage: publish-release.sh VERSION RELEASE_DIRECTORY}"
release_dir="${2:?usage: publish-release.sh VERSION RELEASE_DIRECTORY}"
api="${RENDEZVOUS_GITEA_API:-https://git.finalfactory.de/api/v1}"
registry="${RENDEZVOUS_CONTAINER_REGISTRY:-git.finalfactory.de}"
image="$registry/heikyu/rendezvous:$version"
token="${RENDEZVOUS_RELEASE_TOKEN:?RENDEZVOUS_RELEASE_TOKEN is required}"
username="${RENDEZVOUS_RELEASE_USERNAME:?RENDEZVOUS_RELEASE_USERNAME is required}"
release_builder="${RENDEZVOUS_RELEASE_BUILDER:?RENDEZVOUS_RELEASE_BUILDER is required}"
docker_config="$(mktemp -d)"
curl_config="$(mktemp)"
release_request=""
release_response=""
cleanup() {
[[ -z "$release_request" ]] || rm -f "$release_request"
[[ -z "$release_response" ]] || rm -f "$release_response"
rm -f "$curl_config"
rm -rf "$docker_config"
}
trap cleanup EXIT
chmod 0700 "$docker_config"
chmod 0600 "$curl_config"
printf 'header = "Authorization: token %s"\n' "$token" >"$curl_config"
export DOCKER_CONFIG="$docker_config"
for command in cosign curl docker dotnet jq; do
command -v "$command" >/dev/null || {
echo "Required publication command is unavailable: $command" >&2
exit 1
}
done
run_release_builder() {
docker run --rm \
--user "$(id -u):$(id -g)" \
--volume "$root:/source:ro" \
--volume "$release_dir:$release_dir" \
--workdir /source \
"$release_builder" "$@"
}
"$root/scripts/check-release-tag.sh" "v$version"
"$root/scripts/verify-release.sh" "$version" "$release_dir" publish-ready
require_absent() {
local description="$1"
local url="$2"
local status
status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
--config "$curl_config" "$url")"
if [[ "$status" != 404 ]]; then
echo "$description must not exist before publication (HTTP $status)." >&2
exit 1
fi
}
# Gitea package versions are immutable. Require all destinations to be empty
# before the first write so a tag can never become a silent partial rerun.
require_absent "Client package $version" \
"$api/packages/HeiKyu/nuget/FinalFactory.Rendezvous.Client/$version"
require_absent "Contracts package $version" \
"$api/packages/HeiKyu/nuget/FinalFactory.Rendezvous.Contracts/$version"
require_absent "Container $version" \
"$api/packages/HeiKyu/container/rendezvous/$version"
require_absent "Release v$version" \
"$api/repos/HeiKyu/Rendezvous/releases/tags/v$version"
feed="https://git.finalfactory.de/api/packages/HeiKyu/nuget/index.json"
for package in \
"$release_dir/FinalFactory.Rendezvous.Contracts.$version.nupkg" \
"$release_dir/FinalFactory.Rendezvous.Client.$version.nupkg"; do
dotnet nuget push "$package" \
--source "$feed" \
--api-key "$token" \
--timeout 300
done
printf '%s' "$token" | docker login "$registry" --username "$username" --password-stdin
expected_image_id="$(jq -er '.containerImageId' "$release_dir/release-provenance.json")"
current_image_id="$(docker image inspect --format '{{.Id}}' "$image")"
if [[ "$current_image_id" != "$expected_image_id" ]]; then
echo "Local release tag changed after staging ($expected_image_id -> $current_image_id)." >&2
exit 1
fi
docker push "$image"
digest_ref="$(docker inspect --format '{{index .RepoDigests 0}}' "$image")"
if [[ ! "$digest_ref" =~ ^git\.finalfactory\.de/heikyu/rendezvous@sha256:[0-9a-f]{64}$ ]]; then
echo "Registry did not return an immutable Rendezvous image digest: $digest_ref" >&2
exit 1
fi
run_release_builder python3 eng/release_artifacts.py record-container-digest \
--release-dir "$release_dir" \
--digest "$digest_ref"
cosign public-key --key env://COSIGN_PRIVATE_KEY >"$release_dir/cosign.pub"
run_release_builder ./scripts/finalize-signing-ready-release.sh \
"$version" "$release_dir"
cosign sign --yes --key env://COSIGN_PRIVATE_KEY "$digest_ref"
cosign attest --yes \
--key env://COSIGN_PRIVATE_KEY \
--type https://finalfactory.de/rendezvous/release-provenance/v1 \
--predicate "$release_dir/release-provenance.json" \
"$digest_ref"
cosign sign-blob --yes \
--key env://COSIGN_PRIVATE_KEY \
--bundle "$release_dir/checksums.sha256.bundle" \
"$release_dir/checksums.sha256"
"$root/scripts/verify-release.sh" "$version" "$release_dir" published
cosign verify --key "$release_dir/cosign.pub" "$digest_ref" >/dev/null
cosign verify-attestation \
--key "$release_dir/cosign.pub" \
--type https://finalfactory.de/rendezvous/release-provenance/v1 \
"$digest_ref" >/dev/null
cosign verify-blob \
--key "$release_dir/cosign.pub" \
--bundle "$release_dir/checksums.sha256.bundle" \
"$release_dir/checksums.sha256" >/dev/null
release_request="$(mktemp)"
release_response="$(mktemp)"
prerelease=false
if [[ "$version" == *-* ]]; then
prerelease=true
fi
jq -n \
--arg tag "v$version" \
--arg commit "${GITHUB_SHA:?GITHUB_SHA is required}" \
--arg digest "$digest_ref" \
--argjson prerelease "$prerelease" \
--rawfile changelog "$release_dir/CHANGELOG.md" \
'{tag_name:$tag,target_commitish:$commit,name:("Rendezvous " + $tag),body:($changelog + "\n\n## Immutable container\n\n`" + $digest + "`\n"),draft:false,prerelease:$prerelease}' \
>"$release_request"
curl --fail --silent --show-error \
--request POST \
--config "$curl_config" \
--header 'Content-Type: application/json' \
--data-binary "@$release_request" \
"$api/repos/HeiKyu/Rendezvous/releases" >"$release_response"
release_id="$(jq -er '.id' "$release_response")"
for artifact in "$release_dir"/*; do
curl --fail --silent --show-error \
--request POST \
--config "$curl_config" \
--form "attachment=@$artifact" \
"$api/repos/HeiKyu/Rendezvous/releases/$release_id/assets?name=$(basename "$artifact")" \
>/dev/null
done
echo "Published immutable release v$version with container $digest_ref"
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
version="${1:?usage: verify-real-consumers.sh VERSION RELEASE_DIRECTORY}"
release_dir="${2:?usage: verify-real-consumers.sh VERSION RELEASE_DIRECTORY}"
manifest="$root/eng/consumer-revisions.json"
work="$(mktemp -d "${TMPDIR:-/tmp}/rendezvous-consumers.XXXXXX")"
cleanup() {
rm -rf "$work"
}
trap cleanup EXIT
for command in dotnet git jq python3; do
command -v "$command" >/dev/null || {
echo "Required consumer verification command is unavailable: $command" >&2
exit 1
}
done
python3 "$root/eng/release_artifacts.py" consumer-config \
--local-source "$release_dir" \
--output "$work/NuGet.config"
count="$(jq '.consumers | length' "$manifest")"
for ((index = 0; index < count; index++)); do
name="$(jq -r ".consumers[$index].name" "$manifest")"
repository="$(jq -r ".consumers[$index].repository" "$manifest")"
revision="$(jq -r ".consumers[$index].revision" "$manifest")"
project_relative="$(jq -r ".consumers[$index].project" "$manifest")"
checkout="$work/$name"
git -c init.defaultBranch=main init --quiet "$checkout"
git -C "$checkout" remote add origin "$repository"
git -C "$checkout" fetch --quiet --depth 1 origin "$revision"
GIT_LFS_SKIP_SMUDGE=1 git -C "$checkout" checkout --quiet --detach FETCH_HEAD
[[ "$(git -C "$checkout" rev-parse HEAD)" == "$revision" ]] || {
echo "$name did not resolve the pinned consumer revision." >&2
exit 1
}
project="$checkout/$project_relative"
[[ -f "$project" ]] || {
echo "$name consumer project does not exist at $project_relative." >&2
exit 1
}
targets="$work/$name.Rendezvous.Consumer.targets"
cat >"$targets" <<EOF
<Project>
<ItemGroup Condition="'\$(MSBuildProjectFullPath)' == '$project'">
<PackageReference Include="FinalFactory.Rendezvous.Client" Version="[$version]" />
<PackageReference Include="FinalFactory.Rendezvous.Contracts" Version="[$version]" />
</ItemGroup>
</Project>
EOF
packages="$work/packages-$name"
dotnet restore "$project" \
-p:CustomAfterMicrosoftCommonTargets="$targets" \
-p:RestorePackagesWithLockFile=false \
-p:RestoreLockedMode=false \
--packages "$packages" \
--configfile "$work/NuGet.config" \
--force-evaluate
assets=""
while IFS= read -r candidate_assets; do
if grep -Fq "FinalFactory.Rendezvous.Client/$version" "$candidate_assets"; then
assets="$candidate_assets"
break
fi
done < <(find "$checkout" -path '*/obj/project.assets.json' -type f -print)
[[ -n "$assets" ]] || {
echo "$name restore did not produce assets for the injected Rendezvous references." >&2
exit 1
}
python3 "$root/eng/release_artifacts.py" consumer \
--assets "$assets" \
--version "$version"
echo "Verified $name at $revision can pin and restore Rendezvous $version."
done
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
version="${1:?usage: verify-release.sh VERSION [RELEASE_DIRECTORY] [PHASE]}"
release_dir="${2:-$root/artifacts/release/$version}"
phase="${3:-build}"
python3 "$root/eng/release_artifacts.py" verify \
--root "$root" \
--release-dir "$release_dir" \
--version "$version" \
--phase "$phase"