mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Harden release validation for host agent downloads (related to #735)
This commit is contained in:
parent
9a33dc568e
commit
28c0d3d39c
3 changed files with 445 additions and 102 deletions
88
install.sh
88
install.sh
|
|
@ -2438,7 +2438,7 @@ download_pulse() {
|
|||
print_warn "Host agent binary not found in archive; skipping installation"
|
||||
fi
|
||||
|
||||
install_additional_agent_binaries "$LATEST_RELEASE"
|
||||
install_additional_agent_binaries "$LATEST_RELEASE" "$TEMP_EXTRACT"
|
||||
|
||||
# Install all agent scripts
|
||||
deploy_agent_scripts "$TEMP_EXTRACT"
|
||||
|
|
@ -2487,7 +2487,7 @@ download_pulse() {
|
|||
ln -sf "$INSTALL_DIR/bin/pulse-docker-agent" /usr/local/bin/pulse-docker-agent
|
||||
fi
|
||||
|
||||
install_additional_agent_binaries "$LATEST_RELEASE"
|
||||
install_additional_agent_binaries "$LATEST_RELEASE" "$TEMP_EXTRACT2"
|
||||
|
||||
deploy_agent_scripts "$TEMP_EXTRACT2"
|
||||
|
||||
|
|
@ -2512,26 +2512,74 @@ download_pulse() {
|
|||
fi # End of SKIP_DOWNLOAD check
|
||||
}
|
||||
|
||||
copy_host_agent_binaries_from_dir() {
|
||||
local source_dir="$1"
|
||||
|
||||
if [[ -z "$source_dir" ]] || [[ ! -d "$source_dir/bin" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local copied=0
|
||||
shopt -s nullglob
|
||||
for agent_file in "$source_dir"/bin/pulse-host-agent-*; do
|
||||
[[ -e "$agent_file" ]] || continue
|
||||
|
||||
local base
|
||||
base=$(basename "$agent_file")
|
||||
if [[ "$base" == "pulse-host-agent" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
cp -a "$agent_file" "$INSTALL_DIR/bin/$base"
|
||||
if [[ ! -L "$INSTALL_DIR/bin/$base" ]]; then
|
||||
chmod +x "$INSTALL_DIR/bin/$base"
|
||||
fi
|
||||
chown -h pulse:pulse "$INSTALL_DIR/bin/$base" || true
|
||||
copied=1
|
||||
done
|
||||
shopt -u nullglob
|
||||
|
||||
if [[ $copied -eq 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
install_additional_agent_binaries() {
|
||||
local version="$1"
|
||||
local source_dir="${2:-}"
|
||||
|
||||
if [[ -z "$version" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local targets=("linux-amd64" "linux-arm64" "linux-armv7")
|
||||
local docker_missing=0
|
||||
local host_missing=0
|
||||
for target in "${targets[@]}"; do
|
||||
local docker_targets=("linux-amd64" "linux-arm64" "linux-armv7")
|
||||
local host_targets=("linux-amd64" "linux-arm64" "linux-armv7" "linux-armv6" "linux-386" "darwin-amd64" "darwin-arm64" "windows-amd64" "windows-arm64" "windows-386")
|
||||
|
||||
# Prefer locally available host agents from the extracted archive to avoid network reliance
|
||||
copy_host_agent_binaries_from_dir "$source_dir" || true
|
||||
|
||||
local docker_missing_targets=()
|
||||
for target in "${docker_targets[@]}"; do
|
||||
if [[ ! -f "$INSTALL_DIR/bin/pulse-docker-agent-$target" ]]; then
|
||||
docker_missing=1
|
||||
fi
|
||||
if [[ ! -f "$INSTALL_DIR/bin/pulse-host-agent-$target" ]]; then
|
||||
host_missing=1
|
||||
docker_missing_targets+=("$target")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $docker_missing -eq 0 ]] && [[ $host_missing -eq 0 ]]; then
|
||||
local host_missing_targets=()
|
||||
for target in "${host_targets[@]}"; do
|
||||
if [[ "$target" == windows-* ]]; then
|
||||
if [[ ! -e "$INSTALL_DIR/bin/pulse-host-agent-$target" && ! -e "$INSTALL_DIR/bin/pulse-host-agent-$target.exe" ]]; then
|
||||
host_missing_targets+=("$target")
|
||||
fi
|
||||
else
|
||||
if [[ ! -e "$INSTALL_DIR/bin/pulse-host-agent-$target" ]]; then
|
||||
host_missing_targets+=("$target")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#docker_missing_targets[@]} -eq 0 ]] && [[ ${#host_missing_targets[@]} -eq 0 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
|
|
@ -2582,20 +2630,10 @@ install_additional_agent_binaries() {
|
|||
fi
|
||||
done
|
||||
|
||||
# Install host agent binaries
|
||||
for agent_file in "$temp_dir"/bin/pulse-host-agent-*; do
|
||||
if [[ -f "$agent_file" ]]; then
|
||||
local base
|
||||
base=$(basename "$agent_file")
|
||||
# Don't copy the wrapper script, only platform-specific binaries
|
||||
if [[ "$base" == pulse-host-agent-linux-* ]] || [[ "$base" == pulse-host-agent-darwin-* ]] || [[ "$base" == pulse-host-agent-windows-* ]]; then
|
||||
cp -f "$agent_file" "$INSTALL_DIR/bin/$base"
|
||||
chmod +x "$INSTALL_DIR/bin/$base"
|
||||
chown pulse:pulse "$INSTALL_DIR/bin/$base"
|
||||
host_installed=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
# Install host agent binaries (preserve symlinks for Windows targets)
|
||||
if copy_host_agent_binaries_from_dir "$temp_dir"; then
|
||||
host_installed=1
|
||||
fi
|
||||
|
||||
if [[ $docker_installed -eq 1 ]]; then
|
||||
print_success "Additional Docker agent binaries installed"
|
||||
|
|
|
|||
|
|
@ -34,7 +34,37 @@ rm -rf internal/api/frontend-modern
|
|||
mkdir -p internal/api/frontend-modern
|
||||
cp -r frontend-modern/dist internal/api/frontend-modern/
|
||||
|
||||
# Build for different architectures
|
||||
# Build host agents for every supported platform/architecture so download endpoints work offline
|
||||
echo "Building host agents for all platforms..."
|
||||
declare -A host_agent_builds=(
|
||||
["linux-amd64"]="GOOS=linux GOARCH=amd64"
|
||||
["linux-arm64"]="GOOS=linux GOARCH=arm64"
|
||||
["linux-armv7"]="GOOS=linux GOARCH=arm GOARM=7"
|
||||
["linux-armv6"]="GOOS=linux GOARCH=arm GOARM=6"
|
||||
["linux-386"]="GOOS=linux GOARCH=386"
|
||||
["darwin-amd64"]="GOOS=darwin GOARCH=amd64"
|
||||
["darwin-arm64"]="GOOS=darwin GOARCH=arm64"
|
||||
["windows-amd64"]="GOOS=windows GOARCH=amd64"
|
||||
["windows-arm64"]="GOOS=windows GOARCH=arm64"
|
||||
["windows-386"]="GOOS=windows GOARCH=386"
|
||||
)
|
||||
host_agent_order=(linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-386 darwin-amd64 darwin-arm64 windows-amd64 windows-arm64 windows-386)
|
||||
|
||||
for target in "${host_agent_order[@]}"; do
|
||||
build_env="${host_agent_builds[$target]}"
|
||||
output_path="$BUILD_DIR/pulse-host-agent-$target"
|
||||
if [[ "$target" == windows-* ]]; then
|
||||
output_path="${output_path}.exe"
|
||||
fi
|
||||
|
||||
env $build_env go build \
|
||||
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
|
||||
-trimpath \
|
||||
-o "$output_path" \
|
||||
./cmd/pulse-host-agent
|
||||
done
|
||||
|
||||
# Build for different architectures (server + docker agent + sensor proxy)
|
||||
declare -A builds=(
|
||||
["linux-amd64"]="GOOS=linux GOARCH=amd64"
|
||||
["linux-arm64"]="GOOS=linux GOARCH=arm64"
|
||||
|
|
@ -42,11 +72,11 @@ declare -A builds=(
|
|||
["linux-armv6"]="GOOS=linux GOARCH=arm GOARM=6"
|
||||
["linux-386"]="GOOS=linux GOARCH=386"
|
||||
)
|
||||
build_order=(linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-386)
|
||||
|
||||
for build_name in "${!builds[@]}"; do
|
||||
for build_name in "${build_order[@]}"; do
|
||||
echo "Building for $build_name..."
|
||||
|
||||
# Get build environment
|
||||
build_env="${builds[$build_name]}"
|
||||
|
||||
build_time=$(date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
|
|
@ -66,34 +96,43 @@ for build_name in "${!builds[@]}"; do
|
|||
-o "$BUILD_DIR/pulse-docker-agent-$build_name" \
|
||||
./cmd/pulse-docker-agent
|
||||
|
||||
# Build host agent binary
|
||||
env $build_env go build \
|
||||
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
|
||||
-trimpath \
|
||||
-o "$BUILD_DIR/pulse-host-agent-$build_name" \
|
||||
./cmd/pulse-host-agent
|
||||
|
||||
# Build temperature proxy binary
|
||||
env $build_env go build \
|
||||
-ldflags="-s -w -X main.Version=v${VERSION} -X main.BuildTime=${build_time} -X main.GitCommit=${git_commit}" \
|
||||
-trimpath \
|
||||
-o "$BUILD_DIR/pulse-sensor-proxy-$build_name" \
|
||||
./cmd/pulse-sensor-proxy
|
||||
|
||||
# Create release archive with proper structure
|
||||
done
|
||||
|
||||
# Create platform-specific tarballs that include all host agent binaries for download endpoints
|
||||
for build_name in "${build_order[@]}"; do
|
||||
echo "Packaging release for $build_name..."
|
||||
|
||||
tar_name="pulse-v${VERSION}-${build_name}.tar.gz"
|
||||
|
||||
# Create staging directory
|
||||
staging_dir="$BUILD_DIR/staging-$build_name"
|
||||
rm -rf "$staging_dir"
|
||||
mkdir -p "$staging_dir/bin"
|
||||
mkdir -p "$staging_dir/scripts"
|
||||
|
||||
# Copy binaries and VERSION file
|
||||
|
||||
# Copy architecture-specific runtime binaries
|
||||
cp "$BUILD_DIR/pulse-$build_name" "$staging_dir/bin/pulse"
|
||||
cp "$BUILD_DIR/pulse-docker-agent-$build_name" "$staging_dir/bin/pulse-docker-agent"
|
||||
cp "$BUILD_DIR/pulse-host-agent-$build_name" "$staging_dir/bin/pulse-host-agent"
|
||||
cp "$BUILD_DIR/pulse-sensor-proxy-$build_name" "$staging_dir/bin/pulse-sensor-proxy"
|
||||
|
||||
# Copy host agent binaries for every supported platform/architecture
|
||||
for target in "${host_agent_order[@]}"; do
|
||||
src="$BUILD_DIR/pulse-host-agent-$target"
|
||||
dest="$staging_dir/bin/pulse-host-agent-$target"
|
||||
if [[ "$target" == windows-* ]]; then
|
||||
src="${src}.exe"
|
||||
dest="${dest}.exe"
|
||||
fi
|
||||
cp "$src" "$dest"
|
||||
done
|
||||
( cd "$staging_dir/bin" && ln -sf pulse-host-agent-windows-amd64.exe pulse-host-agent-windows-amd64 && ln -sf pulse-host-agent-windows-arm64.exe pulse-host-agent-windows-arm64 && ln -sf pulse-host-agent-windows-386.exe pulse-host-agent-windows-386 )
|
||||
|
||||
# Copy scripts and VERSION metadata
|
||||
cp "scripts/install-docker-agent.sh" "$staging_dir/scripts/install-docker-agent.sh"
|
||||
cp "scripts/install-container-agent.sh" "$staging_dir/scripts/install-container-agent.sh"
|
||||
cp "scripts/install-host-agent.sh" "$staging_dir/scripts/install-host-agent.sh"
|
||||
|
|
@ -104,15 +143,13 @@ for build_name in "${!builds[@]}"; do
|
|||
cp "scripts/install-docker.sh" "$staging_dir/scripts/install-docker.sh"
|
||||
chmod 755 "$staging_dir/scripts/"*.sh "$staging_dir/scripts/"*.ps1
|
||||
echo "$VERSION" > "$staging_dir/VERSION"
|
||||
|
||||
|
||||
# Create tarball from staging directory
|
||||
cd "$staging_dir"
|
||||
tar -czf "../../$RELEASE_DIR/$tar_name" .
|
||||
cd ../..
|
||||
|
||||
# Cleanup staging
|
||||
|
||||
rm -rf "$staging_dir"
|
||||
|
||||
echo "Created $RELEASE_DIR/$tar_name"
|
||||
done
|
||||
|
||||
|
|
@ -124,7 +161,7 @@ mkdir -p "$universal_dir/bin"
|
|||
mkdir -p "$universal_dir/scripts"
|
||||
|
||||
# Copy all binaries to bin/ directory to maintain consistent structure
|
||||
for build_name in "${!builds[@]}"; do
|
||||
for build_name in "${build_order[@]}"; do
|
||||
cp "$BUILD_DIR/pulse-$build_name" "$universal_dir/bin/pulse-${build_name}"
|
||||
cp "$BUILD_DIR/pulse-docker-agent-$build_name" "$universal_dir/bin/pulse-docker-agent-${build_name}"
|
||||
cp "$BUILD_DIR/pulse-host-agent-$build_name" "$universal_dir/bin/pulse-host-agent-${build_name}"
|
||||
|
|
@ -237,43 +274,6 @@ chmod +x "$universal_dir/bin/pulse-host-agent"
|
|||
# Add VERSION file
|
||||
echo "$VERSION" > "$universal_dir/VERSION"
|
||||
|
||||
# Build host agent for macOS
|
||||
echo "Building host agent for macOS amd64..."
|
||||
env GOOS=darwin GOARCH=amd64 go build \
|
||||
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
|
||||
-trimpath \
|
||||
-o "$BUILD_DIR/pulse-host-agent-darwin-amd64" \
|
||||
./cmd/pulse-host-agent
|
||||
|
||||
echo "Building host agent for macOS arm64..."
|
||||
env GOOS=darwin GOARCH=arm64 go build \
|
||||
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
|
||||
-trimpath \
|
||||
-o "$BUILD_DIR/pulse-host-agent-darwin-arm64" \
|
||||
./cmd/pulse-host-agent
|
||||
|
||||
# Build host agent for Windows
|
||||
echo "Building host agent for Windows amd64..."
|
||||
env GOOS=windows GOARCH=amd64 go build \
|
||||
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
|
||||
-trimpath \
|
||||
-o "$BUILD_DIR/pulse-host-agent-windows-amd64.exe" \
|
||||
./cmd/pulse-host-agent
|
||||
|
||||
echo "Building host agent for Windows arm64..."
|
||||
env GOOS=windows GOARCH=arm64 go build \
|
||||
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
|
||||
-trimpath \
|
||||
-o "$BUILD_DIR/pulse-host-agent-windows-arm64.exe" \
|
||||
./cmd/pulse-host-agent
|
||||
|
||||
echo "Building host agent for Windows 386..."
|
||||
env GOOS=windows GOARCH=386 go build \
|
||||
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
|
||||
-trimpath \
|
||||
-o "$BUILD_DIR/pulse-host-agent-windows-386.exe" \
|
||||
./cmd/pulse-host-agent
|
||||
|
||||
# Package standalone host agent binaries
|
||||
tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-darwin-amd64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-darwin-amd64
|
||||
tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-darwin-arm64
|
||||
|
|
@ -358,3 +358,76 @@ echo
|
|||
echo "Release build complete!"
|
||||
echo "Archives created in $RELEASE_DIR/"
|
||||
ls -lh $RELEASE_DIR/
|
||||
|
||||
# Create host-agent manifest (per tarball) for validation/debugging
|
||||
manifest_path="$RELEASE_DIR/host-agent-manifest.json"
|
||||
echo "Generating host-agent manifest at $manifest_path..."
|
||||
python3 - <<'EOF' "$RELEASE_DIR" "$VERSION" "$manifest_path"
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tarfile
|
||||
|
||||
release_dir = sys.argv[1]
|
||||
version = sys.argv[2]
|
||||
manifest_path = sys.argv[3]
|
||||
|
||||
tar_arches = [
|
||||
"linux-amd64",
|
||||
"linux-arm64",
|
||||
"linux-armv7",
|
||||
"linux-armv6",
|
||||
"linux-386",
|
||||
]
|
||||
|
||||
host_agents = [
|
||||
"pulse-host-agent-linux-amd64",
|
||||
"pulse-host-agent-linux-arm64",
|
||||
"pulse-host-agent-linux-armv7",
|
||||
"pulse-host-agent-linux-armv6",
|
||||
"pulse-host-agent-linux-386",
|
||||
"pulse-host-agent-darwin-amd64",
|
||||
"pulse-host-agent-darwin-arm64",
|
||||
"pulse-host-agent-windows-amd64.exe",
|
||||
"pulse-host-agent-windows-arm64.exe",
|
||||
"pulse-host-agent-windows-386.exe",
|
||||
"pulse-host-agent-windows-amd64",
|
||||
"pulse-host-agent-windows-arm64",
|
||||
"pulse-host-agent-windows-386",
|
||||
]
|
||||
|
||||
manifest = {
|
||||
"version": version,
|
||||
"tarballs": {},
|
||||
"universal": [],
|
||||
}
|
||||
|
||||
def collect_agents(tar_path):
|
||||
found = []
|
||||
try:
|
||||
with tarfile.open(tar_path, "r:gz") as tf:
|
||||
names = set(m.name for m in tf.getmembers() if (m.isfile() or m.issym()))
|
||||
for agent in host_agents:
|
||||
target = f"./bin/{agent}"
|
||||
if target in names:
|
||||
found.append(agent)
|
||||
except Exception as exc:
|
||||
print(f"Failed to read {tar_path}: {exc}", file=sys.stderr)
|
||||
return sorted(found)
|
||||
|
||||
# Platform tarballs
|
||||
for arch in tar_arches:
|
||||
tarball = os.path.join(release_dir, f"pulse-v{version}-{arch}.tar.gz")
|
||||
if os.path.exists(tarball):
|
||||
manifest["tarballs"][arch] = collect_agents(tarball)
|
||||
|
||||
# Universal tarball
|
||||
universal_tar = os.path.join(release_dir, f"pulse-v{version}.tar.gz")
|
||||
if os.path.exists(universal_tar):
|
||||
manifest["universal"] = collect_agents(universal_tar)
|
||||
|
||||
with open(manifest_path, "w", encoding="utf-8") as handle:
|
||||
json.dump(manifest, handle, indent=2)
|
||||
|
||||
print(json.dumps(manifest, indent=2))
|
||||
EOF
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ error() {
|
|||
echo -e "${RED}[ERROR]${NC} $*" >&2
|
||||
}
|
||||
|
||||
section() {
|
||||
echo ""
|
||||
echo -e "${BLUE}=== ${1} ===${NC}"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}[✓]${NC} $*"
|
||||
}
|
||||
|
|
@ -33,6 +38,37 @@ warn() {
|
|||
echo -e "${YELLOW}[WARN]${NC} $*"
|
||||
}
|
||||
|
||||
with_network_blocked() {
|
||||
# Drop outbound traffic inside container by adding a reject route; avoids needing elevated host perms.
|
||||
# Caller supplies: container name, command...
|
||||
local container="$1"
|
||||
shift
|
||||
docker exec "$container" sh -c "ip route add blackhole 0.0.0.0/0 || true" && "$@"
|
||||
}
|
||||
|
||||
check_tar_entries_nonempty() {
|
||||
local tarball="$1"
|
||||
shift
|
||||
for entry in "$@"; do
|
||||
if ! tar -tzf "$tarball" "$entry" >/dev/null 2>&1; then
|
||||
error "$(basename "$tarball") missing entry: $entry"
|
||||
exit 1
|
||||
fi
|
||||
# Examine type; skip size enforcement for symlinks
|
||||
local type
|
||||
type=$(tar -tvf "$tarball" "$entry" 2>/dev/null | awk 'NR==1 {print substr($0,1,1)}')
|
||||
if [ "$type" = "l" ]; then
|
||||
continue
|
||||
fi
|
||||
local size
|
||||
size=$(tar -xOf "$tarball" "$entry" 2>/dev/null | wc -c | tr -d '[:space:]')
|
||||
if [ -z "$size" ] || [ "$size" -le 0 ]; then
|
||||
error "$(basename "$tarball") missing or empty entry: $entry"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
error "Usage: $0 <pulse-version> [image] [release-dir] [--skip-docker]"
|
||||
exit 1
|
||||
|
|
@ -73,7 +109,8 @@ fi
|
|||
|
||||
# Create temp directory for extractions
|
||||
tmp_root=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp_root"' EXIT
|
||||
smoke_container=""
|
||||
trap 'rm -rf "$tmp_root"; if [ -n "$smoke_container" ]; then docker rm -f "$smoke_container" >/dev/null 2>&1 || true; fi' EXIT
|
||||
|
||||
info "Validating Pulse $PULSE_TAG release artifacts"
|
||||
info "Image: $IMAGE"
|
||||
|
|
@ -88,7 +125,7 @@ if [ "$SKIP_DOCKER" = false ]; then
|
|||
|
||||
# Validate VERSION file in container
|
||||
info "Checking VERSION file in Docker image..."
|
||||
docker run --rm --entrypoint /bin/sh -e EXPECTED_VERSION="$PULSE_VERSION" "$IMAGE" -c 'set -euo pipefail; actual=$(cat /VERSION | tr -d "\r\n"); [ "$actual" = "$EXPECTED_VERSION" ] || { echo "VERSION mismatch: expected=$EXPECTED_VERSION actual=$actual" >&2; exit 1; }' || { error "VERSION file mismatch in Docker image"; exit 1; }
|
||||
docker run --rm --entrypoint /bin/sh -e EXPECTED_VERSION="$PULSE_VERSION" "$IMAGE" -c 'set -euo pipefail; for path in /VERSION /app/VERSION; do if [ -f "$path" ]; then actual=$(cat "$path" | tr -d "\r\n"); [ "$actual" = "$EXPECTED_VERSION" ] && exit 0 || { echo "VERSION mismatch at $path: expected=$EXPECTED_VERSION actual=$actual" >&2; exit 1; }; fi; done; echo "VERSION file not found in image" >&2; exit 1' || { error "VERSION file mismatch in Docker image"; exit 1; }
|
||||
success "VERSION file correct: $PULSE_VERSION"
|
||||
|
||||
# Validate all required scripts exist and are executable
|
||||
|
|
@ -120,6 +157,124 @@ if [ "$SKIP_DOCKER" = false ]; then
|
|||
docker run --rm --entrypoint /bin/sh -e EXPECTED_TAG="$PULSE_TAG" "$IMAGE" -c 'set -euo pipefail; grep -aF "$EXPECTED_TAG" /opt/pulse/bin/pulse-docker-agent-linux-amd64 >/dev/null' || { error "Docker agent version string not found"; exit 1; }
|
||||
success "Docker agent version embedded: $PULSE_TAG"
|
||||
|
||||
# Smoke test download endpoints from a running container
|
||||
info "Running download endpoint smoke tests..."
|
||||
HOST_PORT=8765
|
||||
SMOKE_CONTAINER="pulse-download-smoke-$$"
|
||||
smoke_container="$SMOKE_CONTAINER"
|
||||
|
||||
docker run -d --rm \
|
||||
--name "$SMOKE_CONTAINER" \
|
||||
-p "$HOST_PORT:7655" \
|
||||
-e PULSE_MOCK_MODE=true \
|
||||
-e PULSE_ALLOW_DOCKER_UPDATES=true \
|
||||
-e PULSE_AUTH_USER=admin \
|
||||
-e PULSE_AUTH_PASS=admin \
|
||||
"$IMAGE" >/dev/null
|
||||
|
||||
for i in $(seq 1 30); do
|
||||
if curl -fsS "http://127.0.0.1:${HOST_PORT}/api/health" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
if [ "$i" -eq 30 ]; then
|
||||
docker logs "$SMOKE_CONTAINER" || true
|
||||
error "Pulse container did not become healthy for download smoke tests"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
download_matrix=(
|
||||
"linux amd64"
|
||||
"linux arm64"
|
||||
"linux armv7"
|
||||
"linux armv6"
|
||||
"linux 386"
|
||||
"darwin amd64"
|
||||
"darwin arm64"
|
||||
"windows amd64"
|
||||
"windows arm64"
|
||||
"windows 386"
|
||||
)
|
||||
|
||||
for entry in "${download_matrix[@]}"; do
|
||||
set -- $entry
|
||||
platform=$1
|
||||
arch=$2
|
||||
url="http://127.0.0.1:${HOST_PORT}/download/pulse-host-agent?platform=${platform}&arch=${arch}"
|
||||
tmp_file=$(mktemp)
|
||||
if ! curl -fsS -o "$tmp_file" "$url"; then
|
||||
docker logs "$SMOKE_CONTAINER" || true
|
||||
error "Download failed for $platform/$arch"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -s "$tmp_file" ]; then
|
||||
error "Downloaded empty binary for $platform/$arch"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$tmp_file"
|
||||
done
|
||||
success "Download endpoints returned binaries for all platforms/architectures"
|
||||
|
||||
checksum_url="http://127.0.0.1:${HOST_PORT}/download/pulse-host-agent.sha256?platform=linux&arch=amd64"
|
||||
checksum_tmp=$(mktemp)
|
||||
if curl -fsS -o "$checksum_tmp" "$checksum_url"; then
|
||||
if ! grep -Eq '^[0-9a-f]{64}$' "$checksum_tmp"; then
|
||||
error "Invalid checksum response from $checksum_url"
|
||||
exit 1
|
||||
fi
|
||||
success "Checksum endpoint responded with SHA256"
|
||||
else
|
||||
warn "Checksum endpoint unavailable (non-blocking): $checksum_url"
|
||||
fi
|
||||
rm -f "$checksum_tmp"
|
||||
|
||||
docker rm -f "$SMOKE_CONTAINER" >/dev/null 2>&1 || true
|
||||
smoke_container=""
|
||||
|
||||
echo ""
|
||||
|
||||
# Offline self-heal check: run with no outbound network and confirm download endpoint still serves binaries
|
||||
section "Offline self-heal smoke test"
|
||||
SMOKE_CONTAINER="pulse-offline-smoke-$$"
|
||||
smoke_container="$SMOKE_CONTAINER"
|
||||
docker run -d --rm \
|
||||
--name "$SMOKE_CONTAINER" \
|
||||
--network none \
|
||||
-e PULSE_MOCK_MODE=true \
|
||||
-e PULSE_ALLOW_DOCKER_UPDATES=true \
|
||||
-e PULSE_AUTH_USER=admin \
|
||||
-e PULSE_AUTH_PASS=admin \
|
||||
"$IMAGE" >/dev/null
|
||||
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec "$SMOKE_CONTAINER" wget -qO- http://127.0.0.1:7655/api/health >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
if [ "$i" -eq 30 ]; then
|
||||
docker logs "$SMOKE_CONTAINER" || true
|
||||
error "Pulse container did not become healthy for offline smoke tests"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
offline_tmp=$(mktemp)
|
||||
if ! docker exec "$SMOKE_CONTAINER" wget -qO- "http://127.0.0.1:7655/download/pulse-host-agent?platform=linux&arch=amd64" > "$offline_tmp"; then
|
||||
docker logs "$SMOKE_CONTAINER" || true
|
||||
error "Offline self-heal failed: download endpoint returned error with no outbound network"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -s "$offline_tmp" ]; then
|
||||
error "Offline self-heal failed: downloaded binary is empty"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$offline_tmp"
|
||||
success "Offline self-heal: download endpoint works without outbound network"
|
||||
|
||||
docker rm -f "$SMOKE_CONTAINER" >/dev/null 2>&1 || true
|
||||
smoke_container=""
|
||||
|
||||
echo ""
|
||||
else
|
||||
warn "=== Skipping Docker Image Validation (--skip-docker flag provided) ==="
|
||||
|
|
@ -141,6 +296,7 @@ info "Checking required release assets..."
|
|||
required_assets=(
|
||||
"install.sh"
|
||||
"checksums.txt"
|
||||
"host-agent-manifest.json"
|
||||
"pulse-v${PULSE_VERSION}.tar.gz"
|
||||
"pulse-v${PULSE_VERSION}-linux-amd64.tar.gz"
|
||||
"pulse-v${PULSE_VERSION}-linux-arm64.tar.gz"
|
||||
|
|
@ -168,9 +324,92 @@ if [ $missing_count -gt 0 ]; then
|
|||
fi
|
||||
success "All ${#required_assets[@]} required release assets present"
|
||||
|
||||
# Validate host-agent manifest matches expected set
|
||||
section "Validating host-agent manifest"
|
||||
host_agent_manifest="host-agent-manifest.json"
|
||||
python3 - "$host_agent_manifest" "$PULSE_VERSION" <<'EOF' || { error "Host-agent manifest validation failed"; exit 1; }
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
manifest_path = sys.argv[1]
|
||||
version = sys.argv[2]
|
||||
|
||||
with open(manifest_path, "r", encoding="utf-8") as handle:
|
||||
manifest = json.load(handle)
|
||||
|
||||
expected_agents = {
|
||||
"pulse-host-agent-linux-amd64",
|
||||
"pulse-host-agent-linux-arm64",
|
||||
"pulse-host-agent-linux-armv7",
|
||||
"pulse-host-agent-linux-armv6",
|
||||
"pulse-host-agent-linux-386",
|
||||
"pulse-host-agent-darwin-amd64",
|
||||
"pulse-host-agent-darwin-arm64",
|
||||
"pulse-host-agent-windows-amd64.exe",
|
||||
"pulse-host-agent-windows-arm64.exe",
|
||||
"pulse-host-agent-windows-386.exe",
|
||||
"pulse-host-agent-windows-amd64",
|
||||
"pulse-host-agent-windows-arm64",
|
||||
"pulse-host-agent-windows-386",
|
||||
}
|
||||
|
||||
def check_set(name, found):
|
||||
missing = expected_agents - set(found)
|
||||
extra = set(found) - expected_agents
|
||||
if missing or extra:
|
||||
msg = []
|
||||
if missing:
|
||||
msg.append(f"{name} missing: {sorted(missing)}")
|
||||
if extra:
|
||||
msg.append(f"{name} unexpected: {sorted(extra)}")
|
||||
print(" ; ".join(msg))
|
||||
return False
|
||||
return True
|
||||
|
||||
ok = True
|
||||
|
||||
if manifest.get("version") != version:
|
||||
print(f"Manifest version mismatch: expected {version}, got {manifest.get('version')}")
|
||||
ok = False
|
||||
|
||||
universal = manifest.get("universal", [])
|
||||
if not check_set("universal", universal):
|
||||
ok = False
|
||||
|
||||
for arch in ["linux-amd64","linux-arm64","linux-armv7","linux-armv6","linux-386"]:
|
||||
found = manifest.get("tarballs", {}).get(arch)
|
||||
if found is None:
|
||||
print(f"Missing tarball entry in manifest for {arch}")
|
||||
ok = False
|
||||
continue
|
||||
if not check_set(arch, found):
|
||||
ok = False
|
||||
|
||||
if not ok:
|
||||
sys.exit(1)
|
||||
EOF
|
||||
success "Host-agent manifest matches expected platform/arch matrix"
|
||||
|
||||
# Validate tarball contents
|
||||
info "Validating tarball contents..."
|
||||
for arch in linux-amd64 linux-arm64 linux-armv7; do
|
||||
section "Validating tarball contents"
|
||||
tar_arches=(linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-386)
|
||||
host_agent_entries=(
|
||||
./bin/pulse-host-agent-linux-amd64
|
||||
./bin/pulse-host-agent-linux-arm64
|
||||
./bin/pulse-host-agent-linux-armv7
|
||||
./bin/pulse-host-agent-linux-armv6
|
||||
./bin/pulse-host-agent-linux-386
|
||||
./bin/pulse-host-agent-darwin-amd64
|
||||
./bin/pulse-host-agent-darwin-arm64
|
||||
./bin/pulse-host-agent-windows-amd64.exe
|
||||
./bin/pulse-host-agent-windows-arm64.exe
|
||||
./bin/pulse-host-agent-windows-386.exe
|
||||
./bin/pulse-host-agent-windows-amd64
|
||||
./bin/pulse-host-agent-windows-arm64
|
||||
./bin/pulse-host-agent-windows-386
|
||||
)
|
||||
for arch in "${tar_arches[@]}"; do
|
||||
tarball="pulse-v${PULSE_VERSION}-${arch}.tar.gz"
|
||||
|
||||
# Check binaries (note: tarballs use ./ prefix)
|
||||
|
|
@ -179,31 +418,24 @@ for arch in linux-amd64 linux-arm64 linux-armv7; do
|
|||
exit 1
|
||||
fi
|
||||
|
||||
check_tar_entries_nonempty "$tarball" "${host_agent_entries[@]}"
|
||||
|
||||
# Check scripts
|
||||
tar -tzf "$tarball" ./scripts/install-docker-agent.sh ./scripts/install-container-agent.sh ./scripts/install-host-agent.sh ./scripts/install-host-agent.ps1 ./scripts/uninstall-host-agent.sh ./scripts/uninstall-host-agent.ps1 ./scripts/install-sensor-proxy.sh ./scripts/install-docker.sh >/dev/null 2>&1 || { error "$(basename $tarball) missing scripts"; exit 1; }
|
||||
|
||||
# Check VERSION file
|
||||
tar -tzf "$tarball" ./VERSION >/dev/null 2>&1 || { error "$(basename $tarball) missing VERSION file"; exit 1; }
|
||||
done
|
||||
success "Platform-specific tarballs contain all required files"
|
||||
success "Platform-specific tarballs contain all required files (including cross-platform host agents)"
|
||||
|
||||
# Validate universal tarball
|
||||
section "Validating universal tarball"
|
||||
tar -tzf "pulse-v${PULSE_VERSION}.tar.gz" ./VERSION >/dev/null 2>&1 || { error "Universal tarball missing VERSION file"; exit 1; }
|
||||
|
||||
# Validate universal tarball contains Windows/macOS binaries for download endpoint
|
||||
info "Validating universal tarball contains Windows/macOS binaries..."
|
||||
for binary in \
|
||||
./bin/pulse-host-agent-darwin-amd64 \
|
||||
./bin/pulse-host-agent-darwin-arm64 \
|
||||
./bin/pulse-host-agent-windows-amd64.exe \
|
||||
./bin/pulse-host-agent-windows-arm64.exe \
|
||||
./bin/pulse-host-agent-windows-386.exe \
|
||||
./bin/pulse-host-agent-windows-amd64 \
|
||||
./bin/pulse-host-agent-windows-arm64 \
|
||||
./bin/pulse-host-agent-windows-386; do
|
||||
tar -tzf "pulse-v${PULSE_VERSION}.tar.gz" "$binary" >/dev/null 2>&1 || { error "Universal tarball missing $binary"; exit 1; }
|
||||
done
|
||||
success "Universal tarball validated (includes Windows/macOS binaries)"
|
||||
# Validate universal tarball contains all host agent binaries for download endpoint
|
||||
info "Validating universal tarball contains all host agent binaries..."
|
||||
check_tar_entries_nonempty "pulse-v${PULSE_VERSION}.tar.gz" "${host_agent_entries[@]}"
|
||||
success "Universal tarball validated (includes cross-platform host agents)"
|
||||
|
||||
# Validate macOS tarball
|
||||
tar -tzf "pulse-host-agent-v${PULSE_VERSION}-darwin-arm64.tar.gz" pulse-host-agent-darwin-arm64 >/dev/null 2>&1 || { error "macOS tarball validation failed"; exit 1; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue