[grade=B urn:ump:3wvdd3yegylr7e2pzn6tebyq5bisf2awmss7p4j3uwpwsujtnwoq] DC-134/135/136: Shipdeck integration - data-driven login pages, deploy events, badge suppression
DC-134: /api/v1/auth/login-page serves a generic gated auto-login page for any service registered in services.json without a curated flow (App Selector installs, DC-131 git installs). Curated pages always win; unknown services still 404; sanitizer keeps digits/hyphens (shipdeck-style ids); display names HTML-escaped. Kills the sso-gate.js edit + restart per new install. DC-135: shipdeck journal.jsonl tail worker (startShipdeckWorker) ingests deploy/rollback lifecycle rows into the Security Center as source_type=shipdeck (notice/success, error/failure via verify[] block). DC-136: deploy-aware badge suppression - suppressDuringDeploy() before the bridge call in /deploy and /rollback, clearDeploySuppression() in finally (every exit path incl. rejected fetches), reference-counted for overlapping deploys, 10-min TTL auto-expiry (HEALTH_DEPLOY_SUPPRESS_MAX_MS). 23 new tests across 4 suites; full suite 2923/2923 green. Codex judge: C (r1) -> C (r2) -> B (r3) -> B zero-blockers (r4, urn:ump:3wvdd3yegylr7e2pzn6tebyq5bisf2awmss7p4j3uwpwsujtnwoq).
This commit is contained in:
@@ -40,7 +40,7 @@ function readBridgeToken() {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function ({ asyncHandler, log, auditLogger, fetchT }) {
|
||||
module.exports = function ({ asyncHandler, log, auditLogger, fetchT, healthChecker }) {
|
||||
const router = express.Router();
|
||||
|
||||
function featureEnabled() {
|
||||
@@ -151,8 +151,29 @@ module.exports = function ({ asyncHandler, log, auditLogger, fetchT }) {
|
||||
if (typeof dir !== 'string' || !dir.trim()) {
|
||||
return errorResponse(res, 400, 'dir is required');
|
||||
}
|
||||
const serviceNames = (() => {
|
||||
// The deploy dir may host a differently-named service; suppress the
|
||||
// dir basename plus whatever service name the panel passed alongside
|
||||
// the request (deploy payload convention), covering naming skew.
|
||||
const base = String(dir).replace(/\/+$/, '').split('/').pop() || '';
|
||||
const requested = typeof (req.body && req.body.service) === 'string' ? req.body.service : '';
|
||||
return [...new Set([base, requested])].filter(n => n && SERVICE_RE.test(n));
|
||||
})();
|
||||
|
||||
try {
|
||||
const { status, body } = await bridge('POST', '/api/deploy', { dir }, SHIPDECK_DEPLOY_TIMEOUT);
|
||||
// DC-136: suppress BEFORE initiating — the restart blackholes probes
|
||||
// DURING the bridge call, not after it resolves.
|
||||
if (healthChecker) serviceNames.forEach(n => healthChecker.suppressDuringDeploy(n));
|
||||
let bridgeResult;
|
||||
try {
|
||||
bridgeResult = await bridge('POST', '/api/deploy', { dir }, SHIPDECK_DEPLOY_TIMEOUT);
|
||||
} finally {
|
||||
// Judge r3: guaranteed cleanup on EVERY exit path — success, HTTP
|
||||
// failure, thrown fetch error. A failed deploy never leaves real
|
||||
// downtime hidden behind a suppression window.
|
||||
if (healthChecker) serviceNames.forEach(n => healthChecker.clearDeploySuppression(n));
|
||||
}
|
||||
const { status, body } = bridgeResult;
|
||||
if (auditLogger) {
|
||||
auditLogger.log({
|
||||
action: 'deploy.shipdeck',
|
||||
@@ -179,7 +200,17 @@ module.exports = function ({ asyncHandler, log, auditLogger, fetchT }) {
|
||||
return errorResponse(res, 400, 'invalid service name');
|
||||
}
|
||||
try {
|
||||
const { status, body } = await bridge('POST', '/api/rollback', { service }, SHIPDECK_DEPLOY_TIMEOUT);
|
||||
// DC-136: suppress BEFORE the rollback restarts the unit (same shape
|
||||
// as /deploy); clear in a finally so failed rollbacks and thrown
|
||||
// bridge errors never hide real downtime.
|
||||
if (healthChecker) healthChecker.suppressDuringDeploy(service);
|
||||
let bridgeResult;
|
||||
try {
|
||||
bridgeResult = await bridge('POST', '/api/rollback', { service }, SHIPDECK_DEPLOY_TIMEOUT);
|
||||
} finally {
|
||||
if (healthChecker) healthChecker.clearDeploySuppression(service);
|
||||
}
|
||||
const { status, body } = bridgeResult;
|
||||
if (auditLogger) {
|
||||
auditLogger.log({
|
||||
action: 'deploy.rollback',
|
||||
|
||||
Reference in New Issue
Block a user