feat(release): add Alibaba Cloud OSS distribution (#166)
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
# Install a checksum-pinned ossutil 2 binary into a caller-provided directory.
|
||||
# Usage: install-ossutil.sh <bin-dir>
|
||||
set -eu
|
||||
|
||||
BIN_DIR="${1:?usage: install-ossutil.sh <bin-dir>}"
|
||||
VERSION="2.3.0"
|
||||
ARCHIVE="ossutil-$VERSION-linux-amd64.zip"
|
||||
ARCHIVE_SHA256="3ae4d9fc85a7a6e9f5654d1599766f1a3a42a3692870887b5ae9338d582ef65a"
|
||||
DOWNLOAD_URL="https://gosspublic.alicdn.com/ossutil/v2/$VERSION/$ARCHIVE"
|
||||
|
||||
command -v curl >/dev/null 2>&1 || {
|
||||
echo "error: curl is required" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v sha256sum >/dev/null 2>&1 || {
|
||||
echo "error: sha256sum is required" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v unzip >/dev/null 2>&1 || {
|
||||
echo "error: unzip is required" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
curl --proto '=https' --tlsv1.2 -fsSL "$DOWNLOAD_URL" -o "$WORK_DIR/$ARCHIVE"
|
||||
printf '%s %s\n' "$ARCHIVE_SHA256" "$WORK_DIR/$ARCHIVE" | sha256sum -c -
|
||||
unzip -q "$WORK_DIR/$ARCHIVE" -d "$WORK_DIR/extracted"
|
||||
|
||||
OSSUTIL_SOURCE="$(find "$WORK_DIR/extracted" -type f -name ossutil -print -quit)"
|
||||
[ -n "$OSSUTIL_SOURCE" ] || {
|
||||
echo "error: ossutil binary not found in $ARCHIVE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mkdir -p "$BIN_DIR"
|
||||
install -m 0755 "$OSSUTIL_SOURCE" "$BIN_DIR/ossutil"
|
||||
"$BIN_DIR/ossutil" version
|
||||
@@ -0,0 +1,220 @@
|
||||
#!/bin/sh
|
||||
# Mirror the exact assets downloaded from a GitHub Release into Alibaba Cloud OSS.
|
||||
#
|
||||
# Usage: publish-release-to-oss.sh <release-dir> <tag> [update-latest]
|
||||
# update-latest: true only when <tag> is GitHub's current latest Release.
|
||||
#
|
||||
# Required environment:
|
||||
# OSS_BUCKET, OSS_REGION, OSS_ENDPOINT, OSS_PUBLIC_BASE_URL and temporary
|
||||
# OSS_* credentials.
|
||||
set -eu
|
||||
|
||||
RELEASE_DIR="${1:?usage: publish-release-to-oss.sh <release-dir> <tag> [update-latest]}"
|
||||
TAG="${2:?usage: publish-release-to-oss.sh <release-dir> <tag> [update-latest]}"
|
||||
UPDATE_LATEST="${3:-false}"
|
||||
OSSUTIL_BIN="${OSSUTIL_BIN:-ossutil}"
|
||||
|
||||
require_env() {
|
||||
eval "value=\${$1:-}"
|
||||
[ -n "$value" ] || {
|
||||
echo "error: required environment variable $1 is empty" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
for name in OSS_BUCKET OSS_REGION OSS_ENDPOINT OSS_PUBLIC_BASE_URL; do
|
||||
require_env "$name"
|
||||
done
|
||||
|
||||
[ -d "$RELEASE_DIR" ] || {
|
||||
echo "error: release directory not found: $RELEASE_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
case "$TAG" in
|
||||
v[0-9]*) VERSION="${TAG#v}" ;;
|
||||
*)
|
||||
echo "error: release tag must start with v followed by a digit: $TAG" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case "$TAG" in
|
||||
*[!A-Za-z0-9._+-]*|*..*)
|
||||
echo "error: release tag is not safe for an OSS object prefix: $TAG" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case "$UPDATE_LATEST" in
|
||||
true|false) ;;
|
||||
*)
|
||||
echo "error: update-latest must be true or false" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
command -v "$OSSUTIL_BIN" >/dev/null 2>&1 || {
|
||||
echo "error: ossutil not found: $OSSUTIL_BIN" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v sha256sum >/dev/null 2>&1 || {
|
||||
echo "error: sha256sum is required" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
echo "error: jq is required" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
BUNDLES="
|
||||
penguin-linux-x64.tar.gz
|
||||
penguin-linux-arm64.tar.gz
|
||||
penguin-darwin-x64.tar.gz
|
||||
penguin-darwin-arm64.tar.gz
|
||||
penguin-universal.tar.gz
|
||||
penguin-win32-x64.zip
|
||||
"
|
||||
FILES="$BUNDLES
|
||||
penguin-linux-x64.tar.gz.sha256
|
||||
penguin-linux-arm64.tar.gz.sha256
|
||||
penguin-darwin-x64.tar.gz.sha256
|
||||
penguin-darwin-arm64.tar.gz.sha256
|
||||
penguin-universal.tar.gz.sha256
|
||||
penguin-win32-x64.zip.sha256
|
||||
SHA256SUMS
|
||||
install.sh
|
||||
install.ps1
|
||||
"
|
||||
|
||||
for file in $FILES; do
|
||||
[ -f "$RELEASE_DIR/$file" ] || {
|
||||
echo "error: missing GitHub Release asset: $file" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
for bundle in $BUNDLES; do
|
||||
(cd "$RELEASE_DIR" && sha256sum -c "$bundle.sha256")
|
||||
done
|
||||
(cd "$RELEASE_DIR" && sha256sum -c SHA256SUMS)
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
oss_cp() {
|
||||
if [ -n "$3" ]; then
|
||||
"$OSSUTIL_BIN" cp "$1" "$2" \
|
||||
--endpoint "$OSS_ENDPOINT" \
|
||||
--region "$OSS_REGION" \
|
||||
--force \
|
||||
--no-progress \
|
||||
--cache-control "$3"
|
||||
else
|
||||
"$OSSUTIL_BIN" cp "$1" "$2" \
|
||||
--endpoint "$OSS_ENDPOINT" \
|
||||
--region "$OSS_REGION" \
|
||||
--force \
|
||||
--no-progress
|
||||
fi
|
||||
}
|
||||
|
||||
oss_put_if_absent() {
|
||||
local_file="$1"
|
||||
object_key="$2"
|
||||
cache_control="$3"
|
||||
|
||||
"$OSSUTIL_BIN" api put-object \
|
||||
--bucket "$OSS_BUCKET" \
|
||||
--key "$object_key" \
|
||||
--body "file://$local_file" \
|
||||
--forbid-overwrite \
|
||||
--cache-control "$cache_control" \
|
||||
--endpoint "$OSS_ENDPOINT" \
|
||||
--region "$OSS_REGION"
|
||||
}
|
||||
|
||||
file_sha256() {
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
}
|
||||
|
||||
verify_remote_file() {
|
||||
local_file="$1"
|
||||
remote_uri="$2"
|
||||
remote_file="$WORK_DIR/remote-$(basename "$local_file")"
|
||||
rm -f "$remote_file"
|
||||
oss_cp "$remote_uri" "$remote_file" ""
|
||||
local_hash="$(file_sha256 "$local_file")"
|
||||
remote_hash="$(file_sha256 "$remote_file")"
|
||||
[ "$local_hash" = "$remote_hash" ] || {
|
||||
echo "error: OSS object differs from the GitHub Release asset: $remote_uri" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
upload_immutable_file() {
|
||||
local_file="$1"
|
||||
object_key="$2"
|
||||
remote_uri="oss://$OSS_BUCKET/$object_key"
|
||||
existing_file="$WORK_DIR/existing-$(basename "$local_file")"
|
||||
rm -f "$existing_file"
|
||||
|
||||
# An exact-key download avoids needing ListObjects. Existing identical bytes make retries
|
||||
# idempotent; different bytes fail before any upload is attempted.
|
||||
if oss_cp "$remote_uri" "$existing_file" "" >/dev/null 2>&1; then
|
||||
if [ "$(file_sha256 "$local_file")" = "$(file_sha256 "$existing_file")" ]; then
|
||||
echo "Already mirrored: $remote_uri"
|
||||
return
|
||||
fi
|
||||
echo "error: immutable OSS object already exists with different content: $remote_uri" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Uploading: $remote_uri"
|
||||
if ! oss_put_if_absent "$local_file" "$object_key" "public,max-age=31536000,immutable"; then
|
||||
# A concurrent retry may have won the create race. It is safe only if the resulting bytes match.
|
||||
echo "Upload did not create $remote_uri; checking whether an identical object now exists."
|
||||
fi
|
||||
verify_remote_file "$local_file" "$remote_uri"
|
||||
}
|
||||
|
||||
RELEASE_PREFIX="releases/$TAG"
|
||||
for file in $FILES; do
|
||||
upload_immutable_file "$RELEASE_DIR/$file" "$RELEASE_PREFIX/$file"
|
||||
done
|
||||
|
||||
if [ "$UPDATE_LATEST" = "true" ]; then
|
||||
# Re-check at the last possible moment. Another Release can finish while this job is
|
||||
# transferring large assets; an older retry must never roll latest.json backwards.
|
||||
require_env GH_TOKEN
|
||||
require_env GITHUB_REPOSITORY
|
||||
command -v gh >/dev/null 2>&1 || {
|
||||
echo "error: gh is required when updating latest.json" >&2
|
||||
exit 1
|
||||
}
|
||||
CURRENT_LATEST_TAG="$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName)"
|
||||
if [ "$TAG" != "$CURRENT_LATEST_TAG" ]; then
|
||||
echo "Skipping latest.json because GitHub's latest Release changed to $CURRENT_LATEST_TAG."
|
||||
UPDATE_LATEST=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$UPDATE_LATEST" = "true" ]; then
|
||||
PUBLIC_BASE="${OSS_PUBLIC_BASE_URL%/}/$RELEASE_PREFIX"
|
||||
|
||||
jq -n \
|
||||
--arg tag "$TAG" \
|
||||
--arg version "$VERSION" \
|
||||
--arg releaseBaseUrl "$PUBLIC_BASE" \
|
||||
'{
|
||||
schemaVersion: 1,
|
||||
tag: $tag,
|
||||
version: $version,
|
||||
releaseBaseUrl: $releaseBaseUrl
|
||||
}' > "$WORK_DIR/latest.json"
|
||||
|
||||
LATEST_URI="oss://$OSS_BUCKET/latest.json"
|
||||
echo "Updating latest release pointer: $LATEST_URI"
|
||||
oss_cp "$WORK_DIR/latest.json" "$LATEST_URI" "no-cache"
|
||||
verify_remote_file "$WORK_DIR/latest.json" "$LATEST_URI"
|
||||
else
|
||||
echo "Skipping latest.json because update-latest is false."
|
||||
fi
|
||||
|
||||
echo "OSS mirror verified for $TAG."
|
||||
+154
-3
@@ -11,12 +11,19 @@ $Installer = Join-Path $RepoRoot "install.ps1"
|
||||
$WorkDir = Join-Path ([IO.Path]::GetTempPath()) "penguin-installer-tests-$PID"
|
||||
$OriginalPath = $env:Path
|
||||
$OriginalOs = $env:OS
|
||||
$OriginalDownloadBaseUrl = $env:PENGUIN_DOWNLOAD_BASE_URL
|
||||
$OriginalDownloadFallbackBaseUrl = $env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL
|
||||
$OriginalDownloadSource = $env:PENGUIN_DOWNLOAD_SOURCE
|
||||
$OriginalArchive = $env:PENGUIN_ARCHIVE
|
||||
$OriginalInstallDir = $env:PENGUIN_INSTALL_DIR
|
||||
$OriginalVersion = $env:PENGUIN_VERSION
|
||||
$Fixture = @{
|
||||
Requests = [Collections.Generic.List[string]]::new()
|
||||
Mode = "canonical"
|
||||
GoodBundle = $null
|
||||
BadInnerBundle = $null
|
||||
LegacyArchive = $null
|
||||
Installer = $Installer
|
||||
}
|
||||
$global:PenguinInstallerFixture = $Fixture
|
||||
|
||||
@@ -53,13 +60,39 @@ function global:Invoke-WebRequest {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Uri,
|
||||
[Parameter(Mandatory = $true)][string]$OutFile,
|
||||
[switch]$UseBasicParsing
|
||||
[switch]$UseBasicParsing,
|
||||
[int]$TimeoutSec = 0
|
||||
)
|
||||
$f = $global:PenguinInstallerFixture
|
||||
$f.Requests.Add($Uri)
|
||||
if ($f.Mode -eq "404") { throw "fixture 404: $Uri" }
|
||||
if ($f.Mode -eq "network") { throw "fixture network failure: $Uri" }
|
||||
if ($f.Mode -eq "primary-network" -and $Uri -like "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*") {
|
||||
throw "fixture primary network failure"
|
||||
}
|
||||
if ($f.Mode -eq "forced-oss-payload" -and
|
||||
$Uri -like "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*/penguin-*") {
|
||||
throw "fixture forced OSS payload failure"
|
||||
}
|
||||
if ($f.Mode -eq "forwarder-auto-github" -and $Uri -like "*/latest.json") {
|
||||
throw "fixture OSS metadata failure"
|
||||
}
|
||||
switch -Wildcard ($Uri) {
|
||||
"*/latest.json" {
|
||||
if ($f.Mode -eq "forwarder-invalid-metadata") {
|
||||
'{"schemaVersion":1,"tag":"../invalid","releaseBaseUrl":"https://example.invalid"}' |
|
||||
Set-Content -LiteralPath $OutFile -Encoding ascii
|
||||
} else {
|
||||
@{
|
||||
schemaVersion = 1
|
||||
tag = "v0.0.0-test"
|
||||
releaseBaseUrl = "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test"
|
||||
} | ConvertTo-Json | Set-Content -LiteralPath $OutFile -Encoding ascii
|
||||
}
|
||||
}
|
||||
"*/install.ps1" {
|
||||
Copy-Item -LiteralPath $f.Installer -Destination $OutFile
|
||||
}
|
||||
"*/penguin-win32-x64.zip.sha256" {
|
||||
switch ($f.Mode) {
|
||||
"outer-sha-mismatch" {
|
||||
@@ -94,17 +127,54 @@ function Invoke-OnlineCase(
|
||||
$Arguments = @{ InstallDir = $InstallDir }
|
||||
if ($Version) { $Arguments.Version = $Version }
|
||||
$Succeeded = $true
|
||||
try { & $Installer @Arguments *>&1 | Out-Null } catch { $Succeeded = $false }
|
||||
$Output = @()
|
||||
try { $Output = @(& $Installer @Arguments *>&1) } catch { $Succeeded = $false }
|
||||
Assert-True ($Succeeded -eq $ShouldSucceed) "$Name returned an unexpected result"
|
||||
Assert-True ($Fixture.Requests.Count -eq $ExpectedRequests) `
|
||||
"$Name made $($Fixture.Requests.Count) requests, expected $ExpectedRequests"
|
||||
[PSCustomObject]@{ InstallDir = $InstallDir; Requests = @($Fixture.Requests) }
|
||||
[PSCustomObject]@{ InstallDir = $InstallDir; Requests = @($Fixture.Requests); Output = @($Output) }
|
||||
}
|
||||
|
||||
function Invoke-ForwarderCase(
|
||||
[string]$Name,
|
||||
[string]$Mode,
|
||||
[string]$Source,
|
||||
[int]$ExpectedRequests,
|
||||
[string]$Version = "",
|
||||
[bool]$ShouldSucceed = $true
|
||||
) {
|
||||
$Fixture.Mode = $Mode
|
||||
$Fixture.Requests.Clear()
|
||||
$InstallDir = Join-Path $WorkDir "$Name-install"
|
||||
if ($Version) {
|
||||
Remove-Item Env:\PENGUIN_ARCHIVE -ErrorAction SilentlyContinue
|
||||
$env:PENGUIN_VERSION = $Version
|
||||
} else {
|
||||
$env:PENGUIN_ARCHIVE = $Fixture.GoodBundle
|
||||
Remove-Item Env:\PENGUIN_VERSION -ErrorAction SilentlyContinue
|
||||
}
|
||||
$env:PENGUIN_INSTALL_DIR = $InstallDir
|
||||
$env:PENGUIN_DOWNLOAD_SOURCE = $Source
|
||||
Remove-Item Env:\PENGUIN_DOWNLOAD_BASE_URL, Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL -ErrorAction SilentlyContinue
|
||||
$Forwarder = Join-Path $RepoRoot "packages\landing\public\install.ps1"
|
||||
$Output = @()
|
||||
$Succeeded = $true
|
||||
try { $Output = @(& $Forwarder *>&1) } catch { $Succeeded = $false }
|
||||
Assert-True ($Succeeded -eq $ShouldSucceed) "$Name returned an unexpected result"
|
||||
Assert-True ($Fixture.Requests.Count -eq $ExpectedRequests) `
|
||||
"$Name made $($Fixture.Requests.Count) requests, expected $ExpectedRequests"
|
||||
Assert-True (-not (($Output | Out-String) -match 'aliyuncs\.com')) `
|
||||
"$Name exposed the OSS URL in normal output"
|
||||
[PSCustomObject]@{ InstallDir = $InstallDir; Requests = @($Fixture.Requests); Output = @($Output) }
|
||||
}
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
|
||||
# Keep the fixture tests away from the runner's user registry Path.
|
||||
$env:OS = "PenguinInstallerFixtureTest"
|
||||
Remove-Item Env:\PENGUIN_DOWNLOAD_BASE_URL, Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL, `
|
||||
Env:\PENGUIN_DOWNLOAD_SOURCE, Env:\PENGUIN_ARCHIVE, Env:\PENGUIN_INSTALL_DIR, `
|
||||
Env:\PENGUIN_VERSION -ErrorAction SilentlyContinue
|
||||
|
||||
# --- Offline program archive: good install, then a failing upgrade must roll back. ---
|
||||
$InstallDir = Join-Path $WorkDir "offline-installed"
|
||||
@@ -171,6 +241,57 @@ try {
|
||||
"canonical did not request the canonical bundle"
|
||||
$Version = & (Join-Path $canonical.InstallDir "bin\penguin.cmd") --version
|
||||
Assert-True ($Version -eq "fixture-old") "canonical bundle was not installed"
|
||||
|
||||
$env:PENGUIN_DOWNLOAD_BASE_URL = "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test"
|
||||
$override = Invoke-OnlineCase "download-base-override" "canonical" "" $true 2
|
||||
Assert-True ($override.Requests[0] -eq "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test/penguin-win32-x64.zip") `
|
||||
"download base override did not request the configured asset directory"
|
||||
Assert-True (($override.Output | Out-String) -match 'OSS mirror') `
|
||||
"download base override did not identify the OSS mirror"
|
||||
Assert-True (-not (($override.Output | Out-String) -match 'aliyuncs\.com')) `
|
||||
"download base override exposed the OSS URL in normal output"
|
||||
|
||||
$env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL = "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.0.0-test"
|
||||
$fallback = Invoke-OnlineCase "download-fallback" "primary-network" "" $true 3
|
||||
Assert-True ($fallback.Requests[0] -like "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*") `
|
||||
"download fallback did not try the primary source first"
|
||||
Assert-True ($fallback.Requests[1] -like "https://github.com/*/penguin-win32-x64.zip") `
|
||||
"download fallback did not use the same-version GitHub source"
|
||||
Assert-True (-not (($fallback.Output | Out-String) -match 'aliyuncs\.com')) `
|
||||
"download fallback exposed the OSS URL in normal output"
|
||||
Remove-Item Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL
|
||||
Remove-Item Env:\PENGUIN_DOWNLOAD_BASE_URL
|
||||
|
||||
$forwarderOss = Invoke-ForwarderCase "forwarder-oss" "forwarder-oss" "auto" 2
|
||||
Assert-True ($forwarderOss.Requests[0] -like "*/latest.json") `
|
||||
"OSS forwarder did not request release metadata first"
|
||||
Assert-True ($forwarderOss.Requests[1] -like "*/releases/v0.0.0-test/install.ps1") `
|
||||
"OSS forwarder did not request the versioned installer"
|
||||
|
||||
$forwarderGitHub = Invoke-ForwarderCase "forwarder-auto-github" "forwarder-auto-github" "auto" 2
|
||||
Assert-True ($forwarderGitHub.Requests[1] -like "https://github.com/*/releases/latest/download/install.ps1") `
|
||||
"forwarder did not fall back to the GitHub installer"
|
||||
|
||||
$invalidMetadata = Invoke-ForwarderCase "forwarder-invalid-metadata" "forwarder-invalid-metadata" "auto" 2
|
||||
Assert-True ($invalidMetadata.Requests[1] -like "https://github.com/*/releases/latest/download/install.ps1") `
|
||||
"invalid OSS metadata did not fall back to the GitHub installer"
|
||||
|
||||
$forcedGitHub = Invoke-ForwarderCase "forwarder-github" "canonical" "github" 1
|
||||
Assert-True ($forcedGitHub.Requests[0] -like "https://github.com/*/releases/latest/download/install.ps1") `
|
||||
"forced GitHub mode did not request the GitHub installer"
|
||||
|
||||
$forcedOss = Invoke-ForwarderCase "forwarder-forced-oss-no-fallback" `
|
||||
"forced-oss-payload" "oss" 2 "v0.0.0-test" $false
|
||||
Assert-True (-not (($forcedOss.Requests | Out-String) -match 'github\.com')) `
|
||||
"forced OSS mode unexpectedly fell back to GitHub"
|
||||
|
||||
$pinnedForwarder = Invoke-ForwarderCase "forwarder-pinned" "canonical" "auto" 3 "v0.0.0-test"
|
||||
Assert-True ($pinnedForwarder.Requests[0] -like "*/releases/v0.0.0-test/install.ps1") `
|
||||
"pinned forwarder did not request the versioned installer"
|
||||
Assert-True ($pinnedForwarder.Requests[1] -like "*/releases/v0.0.0-test/penguin-win32-x64.zip") `
|
||||
"pinned installer did not keep the selected release version"
|
||||
Remove-Item Env:\PENGUIN_ARCHIVE, Env:\PENGUIN_INSTALL_DIR, Env:\PENGUIN_DOWNLOAD_SOURCE, Env:\PENGUIN_VERSION -ErrorAction SilentlyContinue
|
||||
|
||||
Invoke-OnlineCase "outer-mismatch" "outer-sha-mismatch" "" $false 2 | Out-Null
|
||||
Invoke-OnlineCase "inner-mismatch" "inner-sha-mismatch" "" $false 2 | Out-Null
|
||||
Invoke-OnlineCase "latest-404" "404" "" $false 1 | Out-Null
|
||||
@@ -183,6 +304,36 @@ try {
|
||||
} finally {
|
||||
$env:Path = $OriginalPath
|
||||
$env:OS = $OriginalOs
|
||||
if ($null -eq $OriginalDownloadBaseUrl) {
|
||||
Remove-Item Env:\PENGUIN_DOWNLOAD_BASE_URL -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
$env:PENGUIN_DOWNLOAD_BASE_URL = $OriginalDownloadBaseUrl
|
||||
}
|
||||
if ($null -eq $OriginalDownloadFallbackBaseUrl) {
|
||||
Remove-Item Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
$env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL = $OriginalDownloadFallbackBaseUrl
|
||||
}
|
||||
if ($null -eq $OriginalDownloadSource) {
|
||||
Remove-Item Env:\PENGUIN_DOWNLOAD_SOURCE -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
$env:PENGUIN_DOWNLOAD_SOURCE = $OriginalDownloadSource
|
||||
}
|
||||
if ($null -eq $OriginalArchive) {
|
||||
Remove-Item Env:\PENGUIN_ARCHIVE -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
$env:PENGUIN_ARCHIVE = $OriginalArchive
|
||||
}
|
||||
if ($null -eq $OriginalInstallDir) {
|
||||
Remove-Item Env:\PENGUIN_INSTALL_DIR -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
$env:PENGUIN_INSTALL_DIR = $OriginalInstallDir
|
||||
}
|
||||
if ($null -eq $OriginalVersion) {
|
||||
Remove-Item Env:\PENGUIN_VERSION -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
$env:PENGUIN_VERSION = $OriginalVersion
|
||||
}
|
||||
Remove-Item Function:\Invoke-WebRequest -ErrorAction SilentlyContinue
|
||||
Remove-Variable PenguinInstallerFixture -Scope Global -ErrorAction SilentlyContinue
|
||||
if (Test-Path -LiteralPath $WorkDir) { Remove-Item -LiteralPath $WorkDir -Recurse -Force }
|
||||
|
||||
+109
-2
@@ -242,19 +242,36 @@ url=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-o) output="$2"; shift 2 ;;
|
||||
--connect-timeout | --max-time) shift 2 ;;
|
||||
-*) shift ;;
|
||||
*) url="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
printf '%s\n' "$url" >> "$REQUEST_LOG"
|
||||
base="${url##*/}"
|
||||
case "$MODE:$url" in
|
||||
primary-network:https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*) exit 7 ;;
|
||||
forced-oss-payload:https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*/penguin-*) exit 7 ;;
|
||||
esac
|
||||
case "$MODE:$base" in
|
||||
forwarder-auto-github:latest.json) exit 7 ;;
|
||||
forwarder-invalid-metadata:latest.json)
|
||||
printf '%s\n' '{"schemaVersion":1,"tag":"../invalid","releaseBaseUrl":"https://example.invalid"}' > "$output"
|
||||
;;
|
||||
forwarder-oss:latest.json | forced-oss-payload:latest.json)
|
||||
printf '%s\n' '{"schemaVersion":1,"tag":"v0.0.0-test","releaseBaseUrl":"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test"}' > "$output"
|
||||
;;
|
||||
forwarder-oss:install.sh | forced-oss-payload:install.sh | forwarder-auto-github:install.sh | forwarder-invalid-metadata:install.sh | canonical:install.sh) cp "$ROOT_DIR/install.sh" "$output" ;;
|
||||
404:penguin-*) exit 22 ;;
|
||||
network:penguin-*) exit 7 ;;
|
||||
outer-sha-mismatch:penguin-*.sha256) printf '%064d %s\n' 0 "${base%.sha256}" > "$output" ;;
|
||||
outer-sha-mismatch:penguin-*) cp "$ARTIFACT_DIR/$base" "$output" ;;
|
||||
inner-sha-mismatch:penguin-*.sha256) cp "$BAD_BUNDLE.sha256" "$output" ;;
|
||||
inner-sha-mismatch:penguin-*) cp "$BAD_BUNDLE" "$output" ;;
|
||||
primary-network:penguin-*.sha256) cp "$ARTIFACT_DIR/$base" "$output" ;;
|
||||
primary-network:penguin-*) cp "$ARTIFACT_DIR/$base" "$output" ;;
|
||||
forced-oss-payload:penguin-*.sha256) cp "$ARTIFACT_DIR/$base" "$output" ;;
|
||||
forced-oss-payload:penguin-*) cp "$ARTIFACT_DIR/$base" "$output" ;;
|
||||
legacy:penguin-*.sha256) cp "$LEGACY_ARCHIVE.sha256" "$output" ;;
|
||||
legacy:penguin-*) cp "$LEGACY_ARCHIVE" "$output" ;;
|
||||
canonical:penguin-*.sha256) cp "$ARTIFACT_DIR/$base" "$output" ;;
|
||||
@@ -263,7 +280,7 @@ case "$MODE:$base" in
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$STUB_BIN/curl"
|
||||
export ARTIFACT_DIR BAD_BUNDLE LEGACY_ARCHIVE
|
||||
export ARTIFACT_DIR BAD_BUNDLE LEGACY_ARCHIVE ROOT_DIR
|
||||
|
||||
run_online_case() {
|
||||
name="$1"
|
||||
@@ -271,13 +288,18 @@ run_online_case() {
|
||||
version="$3"
|
||||
expected="$4"
|
||||
expected_requests="$5"
|
||||
download_base_url="${6:-}"
|
||||
download_fallback_base_url="${7:-}"
|
||||
CASE_LOG="$WORK_DIR/$name.log"
|
||||
CASE_OUTPUT="$WORK_DIR/$name.output"
|
||||
CASE_INSTALL="$WORK_DIR/$name-install"
|
||||
: > "$CASE_LOG"
|
||||
set +e
|
||||
REQUEST_LOG="$CASE_LOG" MODE="$mode" PATH="$STUB_BIN:$PATH" \
|
||||
HOME="$WORK_DIR/$name-home" PENGUIN_INSTALL_DIR="$CASE_INSTALL" \
|
||||
PENGUIN_VERSION="$version" sh "$ROOT_DIR/install.sh" >/dev/null 2>&1
|
||||
PENGUIN_VERSION="$version" PENGUIN_DOWNLOAD_BASE_URL="$download_base_url" \
|
||||
PENGUIN_DOWNLOAD_FALLBACK_BASE_URL="$download_fallback_base_url" \
|
||||
sh "$ROOT_DIR/install.sh" >"$CASE_OUTPUT" 2>&1
|
||||
status=$?
|
||||
set -e
|
||||
if [ "$expected" = "success" ]; then
|
||||
@@ -294,6 +316,22 @@ run_online_case canonical canonical "" success 2
|
||||
|| fail_test "canonical online install did not produce a working command"
|
||||
grep -q "/releases/latest/download/$HOST_ASSET\$" "$WORK_DIR/canonical.log" \
|
||||
|| fail_test "canonical did not request the canonical bundle"
|
||||
run_online_case download-base-override canonical "" success 2 \
|
||||
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test" ""
|
||||
grep -q "OSS mirror" "$WORK_DIR/download-base-override.output" \
|
||||
|| fail_test "download base override did not identify the OSS mirror"
|
||||
! grep -q "aliyuncs.com" "$WORK_DIR/download-base-override.output" \
|
||||
|| fail_test "download base override exposed the OSS URL in normal output"
|
||||
run_online_case download-fallback primary-network "" success 3 \
|
||||
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test" \
|
||||
"https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.0.0-test"
|
||||
[ "$(sed -n '1p' "$WORK_DIR/download-fallback.log")" = \
|
||||
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test/$HOST_ASSET" ] \
|
||||
|| fail_test "download fallback did not try the primary source first"
|
||||
grep -q "github.com/.*/releases/download/v0.0.0-test/$HOST_ASSET\$" "$WORK_DIR/download-fallback.log" \
|
||||
|| fail_test "download fallback did not use the same-version GitHub source"
|
||||
! grep -q "aliyuncs.com" "$WORK_DIR/download-fallback.output" \
|
||||
|| fail_test "download fallback exposed the OSS URL in normal output"
|
||||
run_online_case outer-mismatch outer-sha-mismatch "" failure 2
|
||||
run_online_case inner-mismatch inner-sha-mismatch "" failure 2
|
||||
run_online_case latest-404 404 "" failure 1
|
||||
@@ -302,4 +340,73 @@ run_online_case pinned-legacy legacy v0.1.4 success 2
|
||||
grep -q "/releases/download/v0.1.4/$HOST_ASSET\$" "$WORK_DIR/pinned-legacy.log" \
|
||||
|| fail_test "pinned legacy did not request the pinned asset"
|
||||
|
||||
# --- Stable penguin.ooo forwarder: prefer a validated immutable OSS release, but fall back to
|
||||
# GitHub when the metadata probe fails. The real installer uses a local fixture here so the
|
||||
# test isolates bootstrap routing from bundle download behavior above. ---
|
||||
run_forwarder_case() {
|
||||
name="$1"
|
||||
mode="$2"
|
||||
expected_requests="$3"
|
||||
source="${4:-auto}"
|
||||
version="${5:-}"
|
||||
expected="${6:-success}"
|
||||
CASE_LOG="$WORK_DIR/$name.log"
|
||||
CASE_OUTPUT="$WORK_DIR/$name.output"
|
||||
CASE_INSTALL="$WORK_DIR/$name-install"
|
||||
: > "$CASE_LOG"
|
||||
if [ -n "$version" ]; then
|
||||
archive=""
|
||||
else
|
||||
archive="$ARTIFACT_DIR/$HOST_ASSET"
|
||||
fi
|
||||
set +e
|
||||
REQUEST_LOG="$CASE_LOG" MODE="$mode" PATH="$STUB_BIN:$PATH" \
|
||||
HOME="$WORK_DIR/$name-home" PENGUIN_INSTALL_DIR="$CASE_INSTALL" \
|
||||
PENGUIN_ARCHIVE="$archive" PENGUIN_VERSION="$version" \
|
||||
PENGUIN_DOWNLOAD_SOURCE="$source" PENGUIN_DOWNLOAD_BASE_URL="" \
|
||||
PENGUIN_DOWNLOAD_FALLBACK_BASE_URL="" \
|
||||
sh "$ROOT_DIR/packages/landing/public/install.sh" >"$CASE_OUTPUT" 2>&1
|
||||
status=$?
|
||||
set -e
|
||||
if [ "$expected" = "success" ]; then
|
||||
[ "$status" -eq 0 ] || fail_test "$name unexpectedly failed"
|
||||
else
|
||||
[ "$status" -ne 0 ] || fail_test "$name unexpectedly succeeded"
|
||||
fi
|
||||
[ "$(wc -l < "$CASE_LOG" | tr -d ' ')" -eq "$expected_requests" ] \
|
||||
|| fail_test "$name made an unexpected number of requests"
|
||||
! grep -q "aliyuncs.com" "$CASE_OUTPUT" \
|
||||
|| fail_test "$name exposed the OSS URL in normal output"
|
||||
}
|
||||
|
||||
run_forwarder_case forwarder-oss forwarder-oss 2
|
||||
grep -q "/latest.json\$" "$WORK_DIR/forwarder-oss.log" \
|
||||
|| fail_test "OSS forwarder did not request release metadata first"
|
||||
grep -q "/releases/v0.0.0-test/install.sh\$" "$WORK_DIR/forwarder-oss.log" \
|
||||
|| fail_test "OSS forwarder did not request the versioned installer"
|
||||
|
||||
run_forwarder_case forwarder-auto-github forwarder-auto-github 2
|
||||
grep -q "github.com/.*/releases/latest/download/install.sh\$" "$WORK_DIR/forwarder-auto-github.log" \
|
||||
|| fail_test "forwarder did not fall back to the GitHub installer"
|
||||
|
||||
run_forwarder_case forwarder-invalid-metadata forwarder-invalid-metadata 2
|
||||
grep -q "github.com/.*/releases/latest/download/install.sh\$" "$WORK_DIR/forwarder-invalid-metadata.log" \
|
||||
|| fail_test "invalid OSS metadata did not fall back to the GitHub installer"
|
||||
|
||||
run_forwarder_case forwarder-github canonical 1 github
|
||||
grep -q "github.com/.*/releases/latest/download/install.sh\$" "$WORK_DIR/forwarder-github.log" \
|
||||
|| fail_test "forced GitHub mode did not request the GitHub installer"
|
||||
|
||||
run_forwarder_case forwarder-forced-oss-no-fallback forced-oss-payload 2 oss v0.0.0-test failure
|
||||
! grep -q "github.com" "$WORK_DIR/forwarder-forced-oss-no-fallback.log" \
|
||||
|| fail_test "forced OSS mode unexpectedly fell back to GitHub"
|
||||
|
||||
run_forwarder_case forwarder-pinned canonical 3 auto v0.0.0-test
|
||||
[ "$(sed -n '1p' "$WORK_DIR/forwarder-pinned.log")" = \
|
||||
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test/install.sh" ] \
|
||||
|| fail_test "pinned forwarder did not request the versioned installer"
|
||||
[ "$(sed -n '2p' "$WORK_DIR/forwarder-pinned.log")" = \
|
||||
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test/$HOST_ASSET" ] \
|
||||
|| fail_test "pinned installer did not keep the selected release version"
|
||||
|
||||
echo "Installer bundle, offline, rollback and online tests passed."
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
# Verify a GitHub Actions OIDC staging role can round-trip an object under staging/
|
||||
# and cannot write either releases/ or the production latest.json pointer.
|
||||
set -eu
|
||||
|
||||
OSSUTIL_BIN="${OSSUTIL_BIN:-ossutil}"
|
||||
RUN_ID="${GITHUB_RUN_ID:-manual}"
|
||||
RUN_ATTEMPT="${GITHUB_RUN_ATTEMPT:-1}"
|
||||
PREFIX="${1:-staging/$RUN_ID-$RUN_ATTEMPT}"
|
||||
|
||||
require_env() {
|
||||
eval "value=\${$1:-}"
|
||||
[ -n "$value" ] || {
|
||||
echo "error: required environment variable $1 is empty" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
for name in OSS_BUCKET OSS_REGION OSS_ENDPOINT OSS_PUBLIC_BASE_URL; do
|
||||
require_env "$name"
|
||||
done
|
||||
case "$PREFIX" in
|
||||
staging/*) ;;
|
||||
*)
|
||||
echo "error: staging prefix must start with staging/: $PREFIX" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
command -v "$OSSUTIL_BIN" >/dev/null 2>&1 || {
|
||||
echo "error: ossutil not found: $OSSUTIL_BIN" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v curl >/dev/null 2>&1 || {
|
||||
echo "error: curl is required" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
PROBE="$WORK_DIR/oidc-probe.txt"
|
||||
DOWNLOADED="$WORK_DIR/downloaded.txt"
|
||||
printf 'repository=%s\nrun_id=%s\nrun_attempt=%s\ncommit=%s\n' \
|
||||
"${GITHUB_REPOSITORY:-unknown}" "$RUN_ID" "$RUN_ATTEMPT" "${GITHUB_SHA:-unknown}" > "$PROBE"
|
||||
|
||||
oss_cp() {
|
||||
"$OSSUTIL_BIN" cp "$1" "$2" \
|
||||
--endpoint "$OSS_ENDPOINT" \
|
||||
--region "$OSS_REGION" \
|
||||
--force \
|
||||
--no-progress
|
||||
}
|
||||
|
||||
STAGING_URI="oss://$OSS_BUCKET/$PREFIX/oidc-probe.txt"
|
||||
oss_cp "$PROBE" "$STAGING_URI"
|
||||
oss_cp "$STAGING_URI" "$DOWNLOADED"
|
||||
cmp "$PROBE" "$DOWNLOADED"
|
||||
echo "Staging upload/download verified: $STAGING_URI"
|
||||
|
||||
DENIED_URI="oss://$OSS_BUCKET/releases/_staging-deny-probe/$RUN_ID-$RUN_ATTEMPT.txt"
|
||||
if oss_cp "$PROBE" "$DENIED_URI" >"$WORK_DIR/denied.log" 2>&1; then
|
||||
echo "error: staging role unexpectedly wrote to production: $DENIED_URI" >&2
|
||||
echo "Remove that probe manually and fix the RAM policy before continuing." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Eiq 'AccessDenied|Forbidden|(^|[^0-9])403([^0-9]|$)' "$WORK_DIR/denied.log"; then
|
||||
echo "error: production probe failed, but not with a recognizable access-denied response" >&2
|
||||
cat "$WORK_DIR/denied.log" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Production write correctly denied for the staging role."
|
||||
|
||||
# Probe the exact latest.json permission without risking an overwrite. The existing public
|
||||
# object is used as the body and x-oss-forbid-overwrite makes the request non-destructive:
|
||||
# AccessDenied is expected; FileAlreadyExists means the role was incorrectly authorized.
|
||||
LATEST_COPY="$WORK_DIR/latest.json"
|
||||
LATEST_URL="${OSS_PUBLIC_BASE_URL%/}/latest.json"
|
||||
curl --proto '=https' --tlsv1.2 -fsSL "$LATEST_URL" -o "$LATEST_COPY"
|
||||
[ -s "$LATEST_COPY" ] || {
|
||||
echo "error: downloaded latest.json is empty: $LATEST_URL" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if "$OSSUTIL_BIN" api put-object \
|
||||
--bucket "$OSS_BUCKET" \
|
||||
--key latest.json \
|
||||
--body "file://$LATEST_COPY" \
|
||||
--forbid-overwrite \
|
||||
--endpoint "$OSS_ENDPOINT" \
|
||||
--region "$OSS_REGION" >"$WORK_DIR/latest-denied.log" 2>&1; then
|
||||
echo "error: staging role unexpectedly wrote the production latest.json pointer" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -Eiq 'AccessDenied|Forbidden|(^|[^0-9])403([^0-9]|$)' "$WORK_DIR/latest-denied.log"; then
|
||||
echo "Production latest.json write correctly denied for the staging role."
|
||||
elif grep -Eiq 'FileAlreadyExists|(^|[^0-9])409([^0-9]|$)' "$WORK_DIR/latest-denied.log"; then
|
||||
echo "error: staging role is authorized to write latest.json; overwrite was blocked by OSS" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "error: latest.json probe failed, but not with a recognizable access-denied response" >&2
|
||||
cat "$WORK_DIR/latest-denied.log" >&2
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user