mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 21:47:20 +00:00
d1c3ac82e1
Exercise two provider installations with the same tenant ID, unowned-network collision and cleanup isolation. Gate the release-line push before packet freeze and repeat against digest-verified candidate images during container qualification. Update the deployment-installability contract and pin both workflow gates in the release-asset verification suite. Change-source: pulse-maintainer
246 lines
10 KiB
YAML
246 lines
10 KiB
YAML
name: Qualify Exact-Candidate Containers
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
version:
|
|
description: 'Version number without the leading v'
|
|
required: true
|
|
type: string
|
|
container_artifact:
|
|
description: 'Exact-candidate container payload artifact from this run'
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
qualify:
|
|
name: Exact-Candidate Container and Helm Smoke
|
|
# Every channel qualifies on a fresh VM so previous source execution cannot
|
|
# influence the Docker daemon, smoke results or filesystem state.
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
env:
|
|
VERSION: ${{ inputs.version }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
# A reusable workflow inherits github.sha from its caller. Do not
|
|
# accept a caller-controlled checkout ref: the caller event commit is
|
|
# the immutable source identity for both code and candidate payload.
|
|
ref: ${{ github.sha }}
|
|
persist-credentials: false
|
|
|
|
- name: Verify exact caller source
|
|
env:
|
|
EXPECTED_SOURCE_SHA: ${{ github.sha }}
|
|
run: test "$(git rev-parse HEAD)" = "${EXPECTED_SOURCE_SHA}"
|
|
|
|
- name: Download exact-candidate container payload
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: ${{ inputs.container_artifact }}
|
|
path: ${{ runner.temp }}/release-container-payload
|
|
|
|
- name: Verify exact-candidate container payload
|
|
run: |
|
|
python3 scripts/release_candidate_manifest.py verify-local \
|
|
--release-dir "$RUNNER_TEMP/release-container-payload/payload" \
|
|
--manifest "$RUNNER_TEMP/release-container-payload/release-container-payload.json" \
|
|
--version "${VERSION}" \
|
|
--source-sha "${{ github.sha }}"
|
|
|
|
- name: Assemble exact-candidate runtime and agent images
|
|
run: |
|
|
set -euo pipefail
|
|
docker buildx build \
|
|
--target runtime_prebuilt \
|
|
--platform linux/amd64 \
|
|
--build-context "release_payload=${RUNNER_TEMP}/release-container-payload/payload/release" \
|
|
--load \
|
|
--tag "pulse-helm-smoke:${VERSION}" \
|
|
.
|
|
docker buildx build \
|
|
--target agent_runtime_prebuilt \
|
|
--platform linux/amd64 \
|
|
--build-context "release_payload=${RUNNER_TEMP}/release-container-payload/payload/release" \
|
|
--load \
|
|
--tag "pulse-agent-candidate:${VERSION}" \
|
|
.
|
|
docker buildx build \
|
|
--file deploy/provider-msp/Dockerfile.control-plane \
|
|
--target control_plane_prebuilt \
|
|
--platform linux/amd64 \
|
|
--build-context "compiled_payload=${RUNNER_TEMP}/release-container-payload/payload/compiled" \
|
|
--load \
|
|
--tag "pulse-control-plane-candidate:${VERSION}" \
|
|
.
|
|
|
|
- name: Verify container binaries match immutable candidate
|
|
run: |
|
|
set -euo pipefail
|
|
expected_server="$(sha256sum "$RUNNER_TEMP/release-container-payload/payload/release/amd64/bin/pulse" | awk '{print $1}')"
|
|
actual_server="$(docker run --rm --entrypoint /bin/sh "pulse-helm-smoke:${VERSION}" -c 'sha256sum /app/pulse' | awk '{print $1}')"
|
|
expected_agent="$(sha256sum "$RUNNER_TEMP/release-container-payload/payload/release/amd64/bin/pulse-agent-linux-amd64" | awk '{print $1}')"
|
|
actual_embedded_agent="$(docker run --rm --entrypoint /bin/sh "pulse-helm-smoke:${VERSION}" -c 'sha256sum /usr/local/bin/pulse-agent' | awk '{print $1}')"
|
|
actual_agent="$(docker run --rm --entrypoint /bin/sh "pulse-agent-candidate:${VERSION}" -c 'sha256sum /usr/local/bin/pulse-agent' | awk '{print $1}')"
|
|
expected_control_plane="$(sha256sum "$RUNNER_TEMP/release-container-payload/payload/compiled/binaries/pulse-control-plane-linux-amd64" | awk '{print $1}')"
|
|
actual_control_plane="$(docker run --rm --entrypoint /bin/sh "pulse-control-plane-candidate:${VERSION}" -c 'sha256sum /usr/local/bin/pulse-control-plane' | awk '{print $1}')"
|
|
test "${actual_server}" = "${expected_server}"
|
|
test "${actual_embedded_agent}" = "${expected_agent}"
|
|
test "${actual_agent}" = "${expected_agent}"
|
|
test "${actual_control_plane}" = "${expected_control_plane}"
|
|
docker run --rm --entrypoint /bin/sh "pulse-helm-smoke:${VERSION}" -c '
|
|
set -euo pipefail
|
|
test "$(readlink /usr/local/bin/pulse-agent)" = "/opt/pulse/bin/pulse-agent-linux-amd64"
|
|
test -x /usr/local/bin/pulse-agent
|
|
for arch in amd64 arm64 386; do
|
|
for suffix in "" .sig .sshsig; do
|
|
alias="/opt/pulse/bin/pulse-agent-windows-${arch}${suffix}"
|
|
target="pulse-agent-windows-${arch}.exe${suffix}"
|
|
test -L "${alias}"
|
|
test "$(readlink "${alias}")" = "${target}"
|
|
test -s "${alias}"
|
|
done
|
|
done
|
|
for sidecar in /opt/pulse/bin/*.sig /opt/pulse/bin/*.sshsig; do
|
|
test ! -x "$sidecar"
|
|
done
|
|
'
|
|
|
|
- name: Set up Go for provider-network acceptance
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
|
|
- name: Verify two-provider network isolation on live Docker
|
|
env:
|
|
PULSE_RUN_PROVIDER_PAIR_DOCKER_INTEGRATION: '1'
|
|
PULSE_DOCKER_INTEGRATION_IMAGE: pulse-control-plane-candidate:${{ inputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
go test -count=1 -timeout 3m ./internal/cloudcp/docker \
|
|
-run '^TestIntegrationProviderPairNetworkIsolation$' -v \
|
|
| tee "$RUNNER_TEMP/provider-pair-network-test.log"
|
|
# A test with its opt-in gate unset exits successfully as SKIP.
|
|
# Require the exact live test to have run and passed.
|
|
grep -q '^--- PASS: TestIntegrationProviderPairNetworkIsolation ' \
|
|
"$RUNNER_TEMP/provider-pair-network-test.log"
|
|
|
|
- name: Set up Helm
|
|
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
|
|
with:
|
|
version: v3.15.2
|
|
|
|
- name: Helm smoke test with local release-line image
|
|
env:
|
|
SMOKE_IMAGE_REPOSITORY: pulse-helm-smoke
|
|
SMOKE_IMAGE_TAG: ${{ inputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
cleanup() {
|
|
kind delete cluster --name pulse-test >/dev/null 2>&1 || true
|
|
}
|
|
|
|
diagnose() {
|
|
echo "::group::helm status"
|
|
helm status pulse || true
|
|
echo "::endgroup::"
|
|
|
|
echo "::group::kubectl get all"
|
|
kubectl get all -A || true
|
|
echo "::endgroup::"
|
|
|
|
echo "::group::kubectl describe pods"
|
|
kubectl describe pods -A || true
|
|
echo "::endgroup::"
|
|
|
|
echo "::group::pod logs"
|
|
pods=$(kubectl get pods -A -o name 2>/dev/null || true)
|
|
for pod in $pods; do
|
|
echo "### ${pod}"
|
|
kubectl logs --all-containers=true --tail=200 "$pod" || true
|
|
done
|
|
echo "::endgroup::"
|
|
|
|
echo "::group::events"
|
|
kubectl get events -A --sort-by=.lastTimestamp || kubectl get events -A || true
|
|
echo "::endgroup::"
|
|
|
|
cleanup
|
|
}
|
|
|
|
trap 'diagnose' ERR
|
|
|
|
prepare_cluster_tool() {
|
|
local name="$1"
|
|
local url="$2"
|
|
local expected_sha="$3"
|
|
local destination="$RUNNER_TEMP/$name"
|
|
local download="$destination.download"
|
|
local installed_path=""
|
|
installed_path="$(command -v "$name" 2>/dev/null || true)"
|
|
|
|
if [ -n "$installed_path" ] && printf '%s %s\n' "$expected_sha" "$installed_path" | sha256sum --check --status; then
|
|
install -m 0755 "$installed_path" "$destination"
|
|
else
|
|
rm -f "$download"
|
|
curl -fsSL -o "$download" "$url"
|
|
printf '%s %s\n' "$expected_sha" "$download" | sha256sum --check
|
|
chmod 0755 "$download"
|
|
mv "$download" "$destination"
|
|
fi
|
|
|
|
printf '%s %s\n' "$expected_sha" "$destination" | sha256sum --check
|
|
test -x "$destination"
|
|
}
|
|
|
|
prepare_cluster_tool \
|
|
kind \
|
|
https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64 \
|
|
513a7213d6d3332dd9ef27c24dab35e5ef10a04fa27274fe1c14d8a246493ded
|
|
prepare_cluster_tool \
|
|
kubectl \
|
|
https://dl.k8s.io/release/v1.27.3/bin/linux/amd64/kubectl \
|
|
fba6c062e754a120bc8105cde1344de200452fe014a8759e06e4eec7ed258a09
|
|
export PATH="$RUNNER_TEMP:$PATH"
|
|
test "$(command -v kind)" = "$RUNNER_TEMP/kind"
|
|
test "$(command -v kubectl)" = "$RUNNER_TEMP/kubectl"
|
|
kind version
|
|
kubectl version --client=true
|
|
|
|
cleanup
|
|
kind create cluster --name pulse-test --wait 5m
|
|
kind load docker-image "${SMOKE_IMAGE_REPOSITORY}:${SMOKE_IMAGE_TAG}" --name pulse-test
|
|
|
|
helm install pulse deploy/helm/pulse \
|
|
--set persistence.enabled=false \
|
|
--set server.secretEnv.create=true \
|
|
--set server.secretEnv.data.API_TOKENS=test-token \
|
|
--set image.repository="${SMOKE_IMAGE_REPOSITORY}" \
|
|
--set image.tag="${SMOKE_IMAGE_TAG}" \
|
|
--set image.pullPolicy=Never \
|
|
--wait --timeout 5m --debug
|
|
|
|
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=pulse --timeout=180s || (kubectl describe pods -l app.kubernetes.io/name=pulse && exit 1)
|
|
kubectl get pods -l app.kubernetes.io/name=pulse
|
|
|
|
helm upgrade pulse deploy/helm/pulse \
|
|
--set persistence.enabled=false \
|
|
--set server.secretEnv.create=true \
|
|
--set server.secretEnv.data.API_TOKENS=test-token \
|
|
--set image.repository="${SMOKE_IMAGE_REPOSITORY}" \
|
|
--set image.tag="${SMOKE_IMAGE_TAG}" \
|
|
--set image.pullPolicy=Never \
|
|
--wait --timeout 5m --debug
|
|
|
|
trap - ERR
|
|
cleanup
|
|
|
|
echo "✓ Helm smoke test passed"
|