Fix demo verification and agent update recovery
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run

Refs #1515

- restore demo runtime env and verify mock fixtures even when the target version is already installed
- require recovered agent update state to include both URL and token before reporting success
This commit is contained in:
rcourtman 2026-07-04 22:28:01 +01:00
parent 6becdbf9db
commit cd6b250ae6
7 changed files with 279 additions and 19 deletions

View file

@ -1591,6 +1591,10 @@ apply_recovered_agent_arg_value() {
esac
}
recovered_connection_state_ready() {
[[ -n "$PULSE_URL" && -n "$PULSE_TOKEN" ]]
}
recover_connection_state_from_arg_stream() {
local arg=""
local pending_key=""
@ -1688,7 +1692,7 @@ recover_connection_state_from_arg_stream() {
esac
done
[[ "$RECOVERED_AGENT_ARG_STATE" == "true" && -n "$PULSE_URL" ]]
[[ "$RECOVERED_AGENT_ARG_STATE" == "true" ]] && recovered_connection_state_ready
}
recover_connection_state_from_env_stream() {
@ -1738,7 +1742,7 @@ recover_connection_state_from_env_stream() {
esac
done
[[ "$RECOVERED_AGENT_ENV_STATE" == "true" && -n "$PULSE_URL" ]]
[[ "$RECOVERED_AGENT_ENV_STATE" == "true" ]] && recovered_connection_state_ready
}
collect_running_agent_pids() {
@ -1782,7 +1786,7 @@ recover_connection_state_from_running_agent() {
recovered="true"
fi
fi
if [[ "$recovered" == "true" && -n "$PULSE_URL" ]]; then
if [[ "$recovered" == "true" ]] && recovered_connection_state_ready; then
return 0
fi
done < <(collect_running_agent_pids)
@ -1820,7 +1824,7 @@ recover_connection_state_from_systemd_unit() {
;;
esac
done < "$candidate"
if [[ "$recovered" == "true" && -n "$PULSE_URL" ]]; then
if [[ "$recovered" == "true" ]] && recovered_connection_state_ready; then
return 0
fi
done

View file

@ -807,6 +807,12 @@ func TestUpdateDemoWorkflowUsesGovernedNetworkPath(t *testing.T) {
`-name "metrics.db"`,
`Removing demo volatile store: %s`,
`Demo host does not have enough free space to back up $CONFIG_DIR before install.`,
`Restore demo runtime configuration`,
`set_env_value DEMO_MODE true`,
`set_env_value PULSE_MOCK_MODE true`,
`/api/license/runtime-capabilities`,
`Mock mode enabled`,
`Demo server mock mode did not enable after entitlement sync`,
`Verify public browser smoke`,
`./scripts/run_demo_public_browser_smoke.sh`,
}

View file

@ -645,6 +645,9 @@ func TestInstallSHSupportsSavedStateUpdateMode(t *testing.T) {
`recover_connection_state_from_systemd_unit`,
`recover_connection_state_from_arg_stream`,
`recover_connection_state_from_env_stream`,
`recovered_connection_state_ready() {`,
`[[ "$RECOVERED_AGENT_ARG_STATE" == "true" ]] && recovered_connection_state_ready`,
`[[ "$RECOVERED_AGENT_ENV_STATE" == "true" ]] && recovered_connection_state_ready`,
`recover_connection_state_from_existing_agent || true`,
`if [[ -n "$PULSE_URL" && -n "$PULSE_TOKEN" ]]; then`,
`recover_agent_id_from_state_file() {`,
@ -692,6 +695,7 @@ func TestInstallSHRecoversV5ProcessArgsForSavedStateUpdate(t *testing.T) {
RUNTIME_TOKEN_FILE="/var/lib/pulse-agent/token"
` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + `
` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + `
` + extractInstallShellFunction(t, "recovered_connection_state_ready") + `
` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + `
` + extractInstallShellFunction(t, "build_exec_arg_items") + `
` + extractInstallShellFunction(t, "join_exec_arg_items") + `
@ -743,6 +747,144 @@ ARGS
}
}
func TestInstallSHRejectsPartialRecoveredProcessConnectionState(t *testing.T) {
script := `
PULSE_URL=""
PULSE_TOKEN=""
INTERVAL="30s"
INTERVAL_EXPLICIT="false"
ENABLE_HOST="true"
HOST_EXPLICIT="false"
ENABLE_DOCKER=""
DOCKER_EXPLICIT="false"
ENABLE_KUBERNETES=""
KUBERNETES_EXPLICIT="false"
KUBECONFIG_PATH=""
ENABLE_PROXMOX=""
PROXMOX_EXPLICIT="false"
PROXMOX_TYPE=""
INSECURE="false"
ENABLE_COMMANDS="false"
ENROLL="false"
HEALTH_ADDR=""
HEALTH_ADDR_SET="false"
AGENT_ID=""
HOSTNAME_OVERRIDE=""
STATE_DIR="/var/lib/pulse-agent"
CURL_CA_BUNDLE=""
KUBE_INCLUDE_ALL_PODS="false"
KUBE_INCLUDE_ALL_DEPLOYMENTS="false"
DISK_EXCLUDES=()
` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + `
` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + `
` + extractInstallShellFunction(t, "recovered_connection_state_ready") + `
` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + `
if recover_connection_state_from_arg_stream <<'ARGS'
/usr/local/bin/pulse-agent
--url
http://192.168.2.96:7655
--enable-host
--agent-id
agent-123
ARGS
then
echo "UNEXPECTED_SUCCESS"
else
echo "EXPECTED_FAILURE"
fi
printf 'URL=%s\nTOKEN=%s\nAGENT_ID=%s\n' "$PULSE_URL" "$PULSE_TOKEN" "$AGENT_ID"
`
out, err := exec.Command("bash", "-c", script).CombinedOutput()
if err != nil {
t.Fatalf("bash: %v\n%s", err, out)
}
got := string(out)
if !strings.Contains(got, "EXPECTED_FAILURE") {
t.Fatalf("partial recovered state should fail closed:\n%s", got)
}
if strings.Contains(got, "UNEXPECTED_SUCCESS") {
t.Fatalf("partial recovered state reported success:\n%s", got)
}
if !strings.Contains(got, "URL=http://192.168.2.96:7655") || !strings.Contains(got, "AGENT_ID=agent-123") {
t.Fatalf("partial recovery should still preserve non-secret fields for later sources:\n%s", got)
}
if !strings.Contains(got, "TOKEN=") {
t.Fatalf("expected token to remain empty:\n%s", got)
}
}
func TestInstallSHCombinesRecoveredProcessArgsAndEnvConnectionState(t *testing.T) {
script := `
PULSE_URL=""
PULSE_TOKEN=""
INTERVAL="30s"
INTERVAL_EXPLICIT="false"
ENABLE_HOST="true"
HOST_EXPLICIT="false"
ENABLE_DOCKER=""
DOCKER_EXPLICIT="false"
ENABLE_KUBERNETES=""
KUBERNETES_EXPLICIT="false"
KUBECONFIG_PATH=""
ENABLE_PROXMOX=""
PROXMOX_EXPLICIT="false"
PROXMOX_TYPE=""
INSECURE="false"
ENABLE_COMMANDS="false"
ENROLL="false"
HEALTH_ADDR=""
HEALTH_ADDR_SET="false"
AGENT_ID=""
HOSTNAME_OVERRIDE=""
STATE_DIR="/var/lib/pulse-agent"
CURL_CA_BUNDLE=""
KUBE_INCLUDE_ALL_PODS="false"
KUBE_INCLUDE_ALL_DEPLOYMENTS="false"
DISK_EXCLUDES=()
` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + `
` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + `
` + extractInstallShellFunction(t, "recovered_connection_state_ready") + `
` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + `
` + extractInstallShellFunction(t, "recover_connection_state_from_env_stream") + `
if recover_connection_state_from_arg_stream <<'ARGS'
/usr/local/bin/pulse-agent
--url
http://192.168.2.96:7655
ARGS
then
echo "ARGS_READY"
else
echo "ARGS_PARTIAL"
fi
if recover_connection_state_from_env_stream <<'ENV'
PULSE_TOKEN=deadbeef
ENV
then
echo "ENV_READY"
else
echo "ENV_PARTIAL"
fi
printf 'URL=%s\nTOKEN=%s\n' "$PULSE_URL" "$PULSE_TOKEN"
`
out, err := exec.Command("bash", "-c", script).CombinedOutput()
if err != nil {
t.Fatalf("bash: %v\n%s", err, out)
}
got := string(out)
for _, needle := range []string{
"ARGS_PARTIAL",
"ENV_READY",
"URL=http://192.168.2.96:7655",
"TOKEN=deadbeef",
} {
if !strings.Contains(got, needle) {
t.Fatalf("combined recovery missing %q:\n%s", needle, got)
}
}
}
func TestInstallSHUsesCanonicalServiceLifecycleHelpers(t *testing.T) {
content, err := os.ReadFile(repoFile("scripts", "install.sh"))
if err != nil {

View file

@ -735,6 +735,13 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertIn("DEMO_EXPECTED_HOSTNAME", demo)
self.assertIn("Verify target host identity", demo)
self.assertIn("Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.", demo)
self.assertIn("Restore demo runtime configuration", demo)
self.assertIn("set_env_value DEMO_MODE true", demo)
self.assertIn("set_env_value PULSE_MOCK_MODE true", demo)
self.assertIn("Demo service restarted with governed demo runtime configuration.", demo)
self.assertIn("/api/license/runtime-capabilities", demo)
self.assertIn("Mock mode enabled", demo)
self.assertIn("Demo server mock mode did not enable after entitlement sync", demo)
self.assertIn(".resources | if type == \"array\" then length else 0 end", demo)
self.assertNotIn(".nodes | length", demo)
self.assertIn("Mock resources detected", demo)