fix(gateway): finish lifecycle action audits
This commit is contained in:
@@ -1332,11 +1332,6 @@ export const adminRouter = router({
|
|||||||
SHUTDOWN: 'DISABLED',
|
SHUTDOWN: 'DISABLED',
|
||||||
} as const;
|
} as const;
|
||||||
const mappedStatus = statusMap[input.action as keyof typeof statusMap];
|
const mappedStatus = statusMap[input.action as keyof typeof statusMap];
|
||||||
if (mappedStatus) {
|
|
||||||
await ctx.profiles.updateStatus(input.profileName, mappedStatus);
|
|
||||||
await ctx.orchestrator.reconcileNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
const meta = readMetaObject(profile.meta);
|
const meta = readMetaObject(profile.meta);
|
||||||
const actionLog = Array.isArray(meta.adminActions)
|
const actionLog = Array.isArray(meta.adminActions)
|
||||||
? meta.adminActions.filter((entry) => entry && typeof entry === 'object')
|
? meta.adminActions.filter((entry) => entry && typeof entry === 'object')
|
||||||
@@ -1354,6 +1349,23 @@ export const adminRouter = router({
|
|||||||
adminActions: [...actionLog, actionRecord],
|
adminActions: [...actionLog, actionRecord],
|
||||||
};
|
};
|
||||||
await ctx.profiles.updateMeta(input.profileName, nextMeta);
|
await ctx.profiles.updateMeta(input.profileName, nextMeta);
|
||||||
|
if (mappedStatus) {
|
||||||
|
await ctx.profiles.updateStatus(input.profileName, mappedStatus);
|
||||||
|
await ctx.orchestrator.reconcileNow();
|
||||||
|
const appliedActionRecord = {
|
||||||
|
...actionRecord,
|
||||||
|
status: 'APPLIED',
|
||||||
|
handledAt: new Date().toISOString(),
|
||||||
|
handler: 'gateway-api',
|
||||||
|
detail: `profile status reconciled as ${mappedStatus}`,
|
||||||
|
};
|
||||||
|
await ctx.profiles.updateMeta(input.profileName, {
|
||||||
|
...nextMeta,
|
||||||
|
adminActions: [...actionLog, appliedActionRecord],
|
||||||
|
adminActionsUpdatedAt: appliedActionRecord.handledAt,
|
||||||
|
});
|
||||||
|
return { ok: true, action: appliedActionRecord };
|
||||||
|
}
|
||||||
return { ok: true, action: actionRecord };
|
return { ok: true, action: actionRecord };
|
||||||
}),
|
}),
|
||||||
requestBuild: profileAdminProcedure
|
requestBuild: profileAdminProcedure
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const buildCaller = async (
|
|||||||
const createdRuntimeActions: Array<Record<string, unknown>> = [];
|
const createdRuntimeActions: Array<Record<string, unknown>> = [];
|
||||||
const flushes: Array<{ userId: string; reason?: string; iconRevision?: string }> = [];
|
const flushes: Array<{ userId: string; reason?: string; iconRevision?: string }> = [];
|
||||||
const updatedStatuses: GatewayProfileRecord['status'][] = [];
|
const updatedStatuses: GatewayProfileRecord['status'][] = [];
|
||||||
|
const updatedMetas: Record<string, unknown>[] = [];
|
||||||
let reconcileCount = 0;
|
let reconcileCount = 0;
|
||||||
let storedNotice = options.initialNotice ?? '';
|
let storedNotice = options.initialNotice ?? '';
|
||||||
const profile = {
|
const profile = {
|
||||||
@@ -65,7 +66,10 @@ const buildCaller = async (
|
|||||||
return { ...profile, status };
|
return { ...profile, status };
|
||||||
},
|
},
|
||||||
updateBuildStatus: async () => profile,
|
updateBuildStatus: async () => profile,
|
||||||
updateMeta: async () => profile,
|
updateMeta: async (_profileName, meta) => {
|
||||||
|
updatedMetas.push(meta);
|
||||||
|
return profile;
|
||||||
|
},
|
||||||
listReservedToStart: async () => [],
|
listReservedToStart: async () => [],
|
||||||
findQueuedBuild: async () => null,
|
findQueuedBuild: async () => null,
|
||||||
updateLastError: async () => {},
|
updateLastError: async () => {},
|
||||||
@@ -171,6 +175,7 @@ const buildCaller = async (
|
|||||||
admin,
|
admin,
|
||||||
flushes,
|
flushes,
|
||||||
updatedStatuses,
|
updatedStatuses,
|
||||||
|
updatedMetas,
|
||||||
getReconcileCount: () => reconcileCount,
|
getReconcileCount: () => reconcileCount,
|
||||||
getStoredNotice: () => storedNotice,
|
getStoredNotice: () => storedNotice,
|
||||||
setStoredNotice: (notice: string) => {
|
setStoredNotice: (notice: string) => {
|
||||||
@@ -360,6 +365,47 @@ describe('admin runtime clock action API', () => {
|
|||||||
).resolves.toMatchObject({ ok: true });
|
).resolves.toMatchObject({ ok: true });
|
||||||
expect(harness.updatedStatuses).toEqual(['RUNNING']);
|
expect(harness.updatedStatuses).toEqual(['RUNNING']);
|
||||||
expect(harness.getReconcileCount()).toBe(1);
|
expect(harness.getReconcileCount()).toBe(1);
|
||||||
|
expect(harness.updatedMetas).toHaveLength(2);
|
||||||
|
expect(harness.updatedMetas.at(-1)).toMatchObject({
|
||||||
|
adminActions: [
|
||||||
|
{
|
||||||
|
action: 'RESUME',
|
||||||
|
status: 'APPLIED',
|
||||||
|
handler: 'gateway-api',
|
||||||
|
detail: 'profile status reconciled as RUNNING',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['STOP', 'STOPPED'],
|
||||||
|
['SHUTDOWN', 'DISABLED'],
|
||||||
|
] as const)('records %s as terminal only after runtime reconciliation', async (action, expectedStatus) => {
|
||||||
|
const harness = await buildCaller(unusedCreateOperation, { initialProfileStatus: 'RUNNING' });
|
||||||
|
|
||||||
|
const result = await harness.caller.admin.profiles.requestAction({
|
||||||
|
profileName: 'che:2',
|
||||||
|
action,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(harness.updatedStatuses).toEqual([expectedStatus]);
|
||||||
|
expect(harness.getReconcileCount()).toBe(1);
|
||||||
|
expect(harness.updatedMetas).toHaveLength(2);
|
||||||
|
expect(harness.updatedMetas[0]).toMatchObject({
|
||||||
|
adminActions: [{ action, status: 'REQUESTED' }],
|
||||||
|
});
|
||||||
|
expect(harness.updatedMetas[1]).toMatchObject({
|
||||||
|
adminActions: [
|
||||||
|
{
|
||||||
|
action,
|
||||||
|
status: 'APPLIED',
|
||||||
|
handler: 'gateway-api',
|
||||||
|
detail: `profile status reconciled as ${expectedStatus}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(result.action).toMatchObject({ action, status: 'APPLIED', handledAt: expect.any(String) });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects resume outside stopped and paused states without reconciliation', async () => {
|
it('rejects resume outside stopped and paused states without reconciliation', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user