fix(gateway): 프로필 런타임 DB URL을 고정한다
game API와 daemon, worker PM2 정의에 인코딩된 profile DATABASE_URL을 명시한다. migration 성공 뒤 runtime과 rollback이 raw POSTGRES 비밀번호로 실패하는 경로를 회귀 테스트한다.
This commit is contained in:
@@ -467,6 +467,11 @@ export const buildProcessDefinitions = (
|
|||||||
tournament: { name: string; script: string; cwd: string; env: Record<string, string> };
|
tournament: { name: string; script: string; cwd: string; env: Record<string, string> };
|
||||||
} => {
|
} => {
|
||||||
const baseEnv = sanitizeManagedProcessEnv(config.baseEnv ?? {});
|
const baseEnv = sanitizeManagedProcessEnv(config.baseEnv ?? {});
|
||||||
|
const profileDatabaseUrl = resolveGatewayPostgresConfigFromEnv(baseEnv, profile.profile).url;
|
||||||
|
const backendEnv = {
|
||||||
|
...baseEnv,
|
||||||
|
DATABASE_URL: profileDatabaseUrl,
|
||||||
|
};
|
||||||
const frontendName = buildProcessName(profile.profileName, 'frontend');
|
const frontendName = buildProcessName(profile.profileName, 'frontend');
|
||||||
const apiName = buildProcessName(profile.profileName, 'api');
|
const apiName = buildProcessName(profile.profileName, 'api');
|
||||||
const daemonName = buildProcessName(profile.profileName, 'daemon');
|
const daemonName = buildProcessName(profile.profileName, 'daemon');
|
||||||
@@ -483,7 +488,7 @@ export const buildProcessDefinitions = (
|
|||||||
const daemonScript = path.join(daemonCwd, 'dist', 'index.js');
|
const daemonScript = path.join(daemonCwd, 'dist', 'index.js');
|
||||||
const turnDaemonNodeOptions = baseEnv.TURN_DAEMON_NODE_OPTIONS?.trim();
|
const turnDaemonNodeOptions = baseEnv.TURN_DAEMON_NODE_OPTIONS?.trim();
|
||||||
const apiEnv = {
|
const apiEnv = {
|
||||||
...baseEnv,
|
...backendEnv,
|
||||||
POSTGRES_POOL_MAX: managedPostgresPoolMax(baseEnv, 'GAME_API_POSTGRES_POOL_MAX', 4),
|
POSTGRES_POOL_MAX: managedPostgresPoolMax(baseEnv, 'GAME_API_POSTGRES_POOL_MAX', 4),
|
||||||
GAME_API_ROLE: 'server',
|
GAME_API_ROLE: 'server',
|
||||||
PROFILE: profile.profile,
|
PROFILE: profile.profile,
|
||||||
@@ -498,7 +503,7 @@ export const buildProcessDefinitions = (
|
|||||||
GATEWAY_INTERNAL_API_URL: config.gatewayInternalApiUrl,
|
GATEWAY_INTERNAL_API_URL: config.gatewayInternalApiUrl,
|
||||||
};
|
};
|
||||||
const daemonEnv = {
|
const daemonEnv = {
|
||||||
...baseEnv,
|
...backendEnv,
|
||||||
...(turnDaemonNodeOptions ? { NODE_OPTIONS: turnDaemonNodeOptions } : {}),
|
...(turnDaemonNodeOptions ? { NODE_OPTIONS: turnDaemonNodeOptions } : {}),
|
||||||
POSTGRES_POOL_MAX: managedPostgresPoolMax(baseEnv, 'TURN_DAEMON_POSTGRES_POOL_MAX', 2),
|
POSTGRES_POOL_MAX: managedPostgresPoolMax(baseEnv, 'TURN_DAEMON_POSTGRES_POOL_MAX', 2),
|
||||||
GAME_ENGINE_ROLE: 'turn-daemon',
|
GAME_ENGINE_ROLE: 'turn-daemon',
|
||||||
|
|||||||
@@ -250,7 +250,6 @@ describe('buildProcessDefinitions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const definition of Object.values(definitions)) {
|
for (const definition of Object.values(definitions)) {
|
||||||
expect(definition.env).toMatchObject({ DATABASE_URL: 'postgresql://integration.invalid/sammo' });
|
|
||||||
expect(definition.env).not.toHaveProperty('pm_id');
|
expect(definition.env).not.toHaveProperty('pm_id');
|
||||||
expect(definition.env).not.toHaveProperty('args');
|
expect(definition.env).not.toHaveProperty('args');
|
||||||
expect(definition.env).not.toHaveProperty('pm_exec_path');
|
expect(definition.env).not.toHaveProperty('pm_exec_path');
|
||||||
@@ -258,9 +257,46 @@ describe('buildProcessDefinitions', () => {
|
|||||||
expect(definition.env).not.toHaveProperty('NODE_APP_INSTANCE');
|
expect(definition.env).not.toHaveProperty('NODE_APP_INSTANCE');
|
||||||
expect(definition.env).not.toHaveProperty('GATEWAY_ROLE');
|
expect(definition.env).not.toHaveProperty('GATEWAY_ROLE');
|
||||||
}
|
}
|
||||||
|
expect(definitions.frontend.env.DATABASE_URL).toBe('postgresql://integration.invalid/sammo');
|
||||||
|
for (const definition of [
|
||||||
|
definitions.api,
|
||||||
|
definitions.daemon,
|
||||||
|
definitions.auction,
|
||||||
|
definitions.battleSim,
|
||||||
|
definitions.tournament,
|
||||||
|
]) {
|
||||||
|
expect(definition.env.DATABASE_URL).toBe('postgresql://integration.invalid/sammo?schema=che');
|
||||||
|
}
|
||||||
expect(definitions.frontend.env.VITE_APP_BASE_PATH).toBe('/che');
|
expect(definitions.frontend.env.VITE_APP_BASE_PATH).toBe('/che');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('passes the encoded profile database URL to every backend process', () => {
|
||||||
|
const definitions = buildProcessDefinitions(buildProfile(), {
|
||||||
|
...processConfig,
|
||||||
|
baseEnv: {
|
||||||
|
GATEWAY_DATABASE_URL:
|
||||||
|
'postgresql://sammo:encoded%23password@postgres:5432/sammo?schema=public',
|
||||||
|
POSTGRES_USER: 'sammo',
|
||||||
|
POSTGRES_PASSWORD: 'raw#password',
|
||||||
|
POSTGRES_HOST: 'postgres',
|
||||||
|
POSTGRES_PORT: '5432',
|
||||||
|
POSTGRES_DB: 'sammo',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const expectedUrl = 'postgresql://sammo:encoded%23password@postgres:5432/sammo?schema=che';
|
||||||
|
|
||||||
|
for (const definition of [
|
||||||
|
definitions.api,
|
||||||
|
definitions.daemon,
|
||||||
|
definitions.auction,
|
||||||
|
definitions.battleSim,
|
||||||
|
definitions.tournament,
|
||||||
|
]) {
|
||||||
|
expect(definition.env.DATABASE_URL).toBe(expectedUrl);
|
||||||
|
}
|
||||||
|
expect(definitions.frontend.env).not.toHaveProperty('DATABASE_URL');
|
||||||
|
});
|
||||||
|
|
||||||
it('applies a dedicated Node heap option only to the turn daemon', () => {
|
it('applies a dedicated Node heap option only to the turn daemon', () => {
|
||||||
const definitions = buildProcessDefinitions(buildProfile(), {
|
const definitions = buildProcessDefinitions(buildProfile(), {
|
||||||
...processConfig,
|
...processConfig,
|
||||||
|
|||||||
@@ -144,9 +144,11 @@ describe('profile DEPLOY operation', () => {
|
|||||||
];
|
];
|
||||||
const backendProcessNames = processNames.filter((name) => !name.endsWith(':game-frontend'));
|
const backendProcessNames = processNames.filter((name) => !name.endsWith(':game-frontend'));
|
||||||
const running = new Set(processNames);
|
const running = new Set(processNames);
|
||||||
|
const startedDefinitions: Array<Parameters<ProcessManager['start']>[0]> = [];
|
||||||
const processManager: ProcessManager = {
|
const processManager: ProcessManager = {
|
||||||
list: async () => [...running].map((name) => ({ name, status: 'online' })),
|
list: async () => [...running].map((name) => ({ name, status: 'online' })),
|
||||||
start: async (definition) => {
|
start: async (definition) => {
|
||||||
|
startedDefinitions.push(definition);
|
||||||
running.add(definition.name);
|
running.add(definition.name);
|
||||||
},
|
},
|
||||||
stop: async () => {},
|
stop: async () => {},
|
||||||
@@ -225,6 +227,12 @@ describe('profile DEPLOY operation', () => {
|
|||||||
expect(commandGroups[1]?.[0]?.env?.DATABASE_URL).toBe(
|
expect(commandGroups[1]?.[0]?.env?.DATABASE_URL).toBe(
|
||||||
'postgresql://user:encoded%23password@integration.invalid/sammo?schema=che'
|
'postgresql://user:encoded%23password@integration.invalid/sammo?schema=che'
|
||||||
);
|
);
|
||||||
|
expect(startedDefinitions).toHaveLength(backendProcessNames.length);
|
||||||
|
for (const definition of startedDefinitions) {
|
||||||
|
expect(definition.env?.DATABASE_URL).toBe(
|
||||||
|
'postgresql://user:encoded%23password@integration.invalid/sammo?schema=che'
|
||||||
|
);
|
||||||
|
}
|
||||||
expect(commandGroups.flat().some((command) => command.env?.GATEWAY_ROLE === 'profile-seed')).toBe(false);
|
expect(commandGroups.flat().some((command) => command.env?.GATEWAY_ROLE === 'profile-seed')).toBe(false);
|
||||||
expect(patches.at(-1)).toMatchObject({
|
expect(patches.at(-1)).toMatchObject({
|
||||||
buildStatus: 'SUCCEEDED',
|
buildStatus: 'SUCCEEDED',
|
||||||
|
|||||||
Reference in New Issue
Block a user