Skip to content

Commit

Permalink
feat: tenant health-check + storage version (#376)
Browse files Browse the repository at this point in the history
  • Loading branch information
fenos authored Oct 9, 2023
1 parent 446f5f4 commit 1cc084a
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,7 @@ jobs:
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
build-args: |
VERSION=${{ needs.release.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
5 changes: 4 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface buildOpts extends FastifyServerOptions {
exposeDocs?: boolean
}

const { keepAliveTimeout, headersTimeout, isMultitenant } = getConfig()
const { version, keepAliveTimeout, headersTimeout, isMultitenant } = getConfig()

const build = (opts: buildOpts = {}): FastifyInstance => {
const app = fastify(opts)
Expand Down Expand Up @@ -66,6 +66,9 @@ const build = (opts: buildOpts = {}): FastifyInstance => {

setErrorHandler(app)

app.get('/version', (_, reply) => {
reply.send(version)
})
app.get('/status', async (request, response) => response.status(200).send())

return app
Expand Down
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import dotenv from 'dotenv'
type StorageBackendType = 'file' | 's3'

type StorageConfigType = {
version: string
keepAliveTimeout: number
headersTimeout: number
adminApiKeys: string
Expand Down Expand Up @@ -97,6 +98,7 @@ export function getConfig(): StorageConfigType {
dotenv.config()

return {
version: getOptionalConfigFromEnv('VERSION') || '0.0.0',
keepAliveTimeout: parseInt(getOptionalConfigFromEnv('SERVER_KEEP_ALIVE_TIMEOUT') || '61', 10),
headersTimeout: parseInt(getOptionalConfigFromEnv('SERVER_HEADERS_TIMEOUT') || '65', 10),
adminApiKeys: getOptionalConfigFromEnv('ADMIN_API_KEYS') || '',
Expand Down
2 changes: 1 addition & 1 deletion src/http/plugins/tenant-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const tenantId = fastifyPlugin(async (fastify) => {
})

export const adminTenantId = fastifyPlugin(async (fastify) => {
fastify.decorateRequest('tenantId', tenantId)
fastify.register(tenantId)
fastify.addHook('onRequest', async (request) => {
const tenantId = (request.params as Record<string, undefined | string>).tenantId
if (!tenantId) return
Expand Down
64 changes: 63 additions & 1 deletion src/http/routes/tenant/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@ import { FromSchema } from 'json-schema-to-ts'
import apiKey from '../../plugins/apikey'
import { decrypt, encrypt } from '../../../auth'
import { knex } from '../../../database/multitenant-db'
import { deleteTenantConfig, runMigrations } from '../../../database/tenant'
import {
deleteTenantConfig,
getServiceKeyUser,
getTenantConfig,
runMigrations,
} from '../../../database/tenant'
import { TenantConnection } from '../../../database/connection'
import { Knex } from 'knex'
import { getConfig } from '../../../config'
import { StorageBackendError } from '../../../storage'

const { databaseMaxConnections } = getConfig()

const patchSchema = {
body: {
Expand Down Expand Up @@ -245,4 +256,55 @@ export default async function routes(fastify: FastifyInstance) {
deleteTenantConfig(request.params.tenantId)
reply.code(204).send()
})

fastify.get<tenantRequestInterface>('/:tenantId/health', async (req, res) => {
let isExternalPool = false
let connection: TenantConnection | undefined
let tx: Knex.Transaction | undefined

try {
const { databasePoolUrl, databaseUrl } = await getTenantConfig(req.params.tenantId)
const adminUser = await getServiceKeyUser(req.params.tenantId)

isExternalPool = Boolean(databasePoolUrl)
connection = await TenantConnection.create({
tenantId: req.params.tenantId,
headers: {},
maxConnections: isExternalPool ? 1 : databaseMaxConnections,
dbUrl: databasePoolUrl || databaseUrl,
isExternalPool,
user: adminUser,
superUser: adminUser,
})
} catch (e) {
if (e instanceof StorageBackendError) {
res.status(200).send({ healthy: false, error: e.message })
return
}
res.status(200).send({ healthy: false, error: 'Could not create connection' })
return
}

try {
tx = await connection.transaction()
} catch (e) {
res.status(500).send({ healthy: false, error: 'Could not acquire connection' })
return
}

try {
await tx.raw('SELECT id from storage.buckets limit 1')
await tx.commit()
res.send({ healthy: true })
} catch (e) {
await tx.rollback(e)
res.status(200).send({ healthy: false })
} finally {
if (isExternalPool && connection) {
connection.dispose().catch(() => {
// ignore
})
}
}
})
}

0 comments on commit 1cc084a

Please sign in to comment.