Skip to content

Commit

Permalink
feat: Handle termination signals (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
meyfa authored Jul 19, 2024
1 parent 08d91bd commit a257d4b
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 45 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ EXPOSE 8080

# use tini as init process since Node.js isn't designed to be run as PID 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "--disable-proto=delete", "dist/server.js"]
CMD ["node", "--disable-proto=delete", "dist/main.js"]
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"build": "npm run build --workspaces && node -e \"fs.rmSync('./dist',{force:true,recursive:true})\" && tsc",
"lint": "npm run lint --workspaces && tsc --noEmit -p tsconfig.lint.json && eslint --ignore-path .gitignore src",
"test": "npm run test --workspaces --if-present && mocha --require tsx --recursive \"test/**/*.ts\"",
"start": "node --disable-proto=delete dist/server.js"
"start": "node --disable-proto=delete dist/main.js"
},
"repository": {
"type": "git",
Expand Down
89 changes: 89 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import path from 'node:path'
import { Config, readConfigDirectory } from './config.js'
import { StructError } from 'superstruct'
import pino from 'pino'
import { startServer } from './server.js'
import { getPort } from './environment.js'
import { KubeConfig } from '@kubernetes/client-node'
import { loadKubeConfig } from './kube-config.js'

const log = pino({
level: 'info',
// do not log pid and hostname
base: undefined,
// use ISO strings for timestamps instead of milliseconds
timestamp: pino.stdTimeFunctions.isoTime,
// use string levels (e.g., "info") instead of level numbers (e.g., 30)
formatters: {
level: (label) => ({ level: label }),
log: (object) => {
if (object instanceof Error) {
return pino.stdSerializers.errWithCause(object)
}
return object
}
}
})

log.info('process_start')

let config: Config
try {
config = await readConfigDirectory(path.join(process.cwd(), 'config'))
} catch (error) {
if (error instanceof StructError) {
// the value may contain secrets, so don't log it
error.value = undefined
error.branch = []
}
log.fatal(error, 'config_error')
process.exit(1)
}

let port: number
try {
port = getPort()
} catch (error) {
log.fatal(error, 'config_error')
process.exit(1)
}

let kubeConfig: KubeConfig
try {
kubeConfig = loadKubeConfig(log, config)
} catch (error) {
log.fatal(error, 'config_error')
process.exit(1)
}

const abortController = new AbortController()
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
process.once(signal, () => {
log.info({ signal }, 'process_signal')
abortController.abort()
})
}

try {
const close = await startServer({
log,
config,
port,
kubeConfig
})

if (abortController.signal.aborted) {
await close()
} else {
abortController.signal.addEventListener('abort', () => {
close().then(() => {
log.info('server_closed')
}).catch((error) => {
log.error(error, 'server_close_error')
})
})
}
} catch (error) {
log.fatal(error, 'uncaught_error')
process.exit(1)
}
58 changes: 15 additions & 43 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,20 @@ import { backend, notFound } from 'backend'
import { fastifyStatic } from '@fastify/static'
import path from 'node:path'
import { fastify } from 'fastify'
import { Config, readConfigDirectory } from './config.js'
import { getPort } from './environment.js'
import { loadKubeConfig } from './kube-config.js'
import { StructError } from 'superstruct'
import { Config } from './config.js'
import { KubeConfig } from '@kubernetes/client-node'
import { handleError } from './handle-error.js'
import pino from 'pino'
import { BaseLogger } from 'pino'

const log = pino({
level: 'info',
// do not log pid and hostname
base: undefined,
// use ISO strings for timestamps instead of milliseconds
timestamp: pino.stdTimeFunctions.isoTime,
// use string levels (e.g., "info") instead of level numbers (e.g., 30)
formatters: {
level: (label) => ({ level: label }),
log: (object) => {
if (object instanceof Error) {
return pino.stdSerializers.errWithCause(object)
}
return object
}
}
})

log.info('process_start')

let config: Config
try {
config = await readConfigDirectory(path.join(process.cwd(), 'config'))
} catch (error) {
if (error instanceof StructError) {
// the value may contain secrets, so don't log it
error.value = undefined
error.branch = []
}
log.fatal(error, 'config_error')
process.exit(1)
}
type CloseFunction = () => Promise<void>

try {
const port = getPort()
const kubeConfig = loadKubeConfig(log, config)
export async function startServer (options: {
log: BaseLogger
config: Config
port: number
kubeConfig: KubeConfig
}): Promise<CloseFunction> {
const { log, config, port, kubeConfig } = options

const app = fastify({
logger: log
Expand Down Expand Up @@ -85,7 +56,8 @@ try {
})

await app.listen({ port, host: '::' })
} catch (error) {
log.fatal(error, 'uncaught_error')
process.exit(1)

return async () => {
await app.close()
}
}

0 comments on commit a257d4b

Please sign in to comment.