Skip to content

Commit 9075ee7

Browse files
committed
rebase
2 parents 6ba2add + 0fb6962 commit 9075ee7

File tree

17 files changed

+401
-344
lines changed

17 files changed

+401
-344
lines changed

.dockerignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
dist
3-
.env
3+
.env
4+
migrations/tenants-migration-hash.txt

Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ COPY migrations migrations
1919
COPY ecosystem.config.js package.json ./
2020
COPY --from=0 /app/node_modules node_modules
2121
COPY --from=1 /app/dist dist
22+
23+
RUN node dist/scripts/migration-hash.js
24+
RUN export DB_MIGRATION_HASH=$(echo "$(cut ./dist/migrations/tenants-migration-hash.txt)")
25+
26+
ENV DB_MIGRATION_HASH=$DB_MIGRATION_HASH
27+
2228
EXPOSE 5000
2329
ENTRYPOINT ["docker-entrypoint.sh"]
2430
CMD ["node", "dist/server.js"]

src/admin-app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const build = (opts: FastifyServerOptions = {}, appInstance?: FastifyInstance):
77
app.register(plugins.adminTenantId)
88
app.register(plugins.logTenantId)
99
app.register(plugins.logRequest({ excludeUrls: ['/status', '/metrics', '/health'] }))
10-
app.register(routes.tenant, { prefix: 'tenants' })
10+
app.register(routes.tenants, { prefix: 'tenants' })
11+
app.register(routes.migrations, { prefix: 'migrations' })
1112

1213
let registriesToMerge: Registry[] = []
1314

src/config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ type StorageConfigType = {
3131
dbSuperUser: string
3232
dbSearchPath: string
3333
dbMigrationHash?: string
34-
dbDisableTenantMigrations: boolean
3534
databaseURL: string
3635
databaseSSLRootCert?: string
3736
databasePoolURL?: string
@@ -236,7 +235,6 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType {
236235
),
237236
dbSuperUser: getOptionalConfigFromEnv('DB_SUPER_USER') || 'postgres',
238237
dbMigrationHash: getOptionalConfigFromEnv('DB_MIGRATION_HASH'),
239-
dbDisableTenantMigrations: getOptionalConfigFromEnv('DB_DISABLE_TENANT_MIGRATIONS') === 'true',
240238

241239
// Database - Connection
242240
dbSearchPath: getOptionalConfigFromEnv('DATABASE_SEARCH_PATH', 'DB_SEARCH_PATH') || '',

src/database/migrate.ts

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import { Client, ClientConfig } from 'pg'
22
import { loadMigrationFiles, MigrationError } from 'postgres-migrations'
33
import { getConfig } from '../config'
4-
import { logger } from '../monitoring'
4+
import { logger, logSchema } from '../monitoring'
55
import { BasicPgClient, Migration } from 'postgres-migrations/dist/types'
66
import { validateMigrationHashes } from 'postgres-migrations/dist/validation'
77
import { runMigration } from 'postgres-migrations/dist/run-migration'
88
import SQL from 'sql-template-strings'
99
import { searchPath } from './connection'
10-
import { updateTenantMigrationVersion } from './tenant'
10+
import { listTenantsToMigrate, updateTenantMigrationVersion } from './tenant'
11+
import { knex } from './multitenant-db'
12+
import { RunMigrationsOnTenants } from '../queue'
1113

1214
const {
1315
isMultitenant,
1416
multitenantDatabaseUrl,
17+
pgQueueEnable,
1518
databaseSSLRootCert,
1619
dbAnonRole,
1720
dbAuthenticatedRole,
@@ -34,6 +37,43 @@ const backportMigrations = [
3437
},
3538
]
3639

40+
/**
41+
* Runs migrations for all tenants
42+
* only one instance at the time is allowed to run
43+
*/
44+
export async function runMigrationsOnAllTenants() {
45+
if (pgQueueEnable) {
46+
return
47+
}
48+
const result = await knex.raw(`SELECT pg_try_advisory_lock(?);`, ['-8575985245963000605'])
49+
const lockAcquired = result.rows.shift()?.pg_try_advisory_lock || false
50+
51+
if (!lockAcquired) {
52+
return
53+
}
54+
55+
try {
56+
const tenants = listTenantsToMigrate()
57+
for await (const tenantBatch of tenants) {
58+
await Promise.allSettled(
59+
tenantBatch.map((tenant) => {
60+
return RunMigrationsOnTenants.send({
61+
tenantId: tenant,
62+
singletonKey: tenant,
63+
tenant: {
64+
ref: tenant,
65+
},
66+
})
67+
})
68+
)
69+
}
70+
} finally {
71+
try {
72+
await knex.raw(`SELECT pg_advisory_unlock(?);`, ['-8575985245963000605'])
73+
} catch (e) {}
74+
}
75+
}
76+
3777
/**
3878
* Runs multi-tenant migrations
3979
*/
@@ -46,15 +86,20 @@ export async function runMultitenantMigrations(): Promise<void> {
4686
/**
4787
* Runs migrations on a specific tenant by providing its database DSN
4888
* @param databaseUrl
89+
* @param tenantId
4990
*/
50-
export async function runMigrationsOnTenant(databaseUrl: string): Promise<void> {
91+
export async function runMigrationsOnTenant(databaseUrl: string, tenantId?: string): Promise<void> {
5192
let ssl: ClientConfig['ssl'] | undefined = undefined
5293

5394
if (databaseSSLRootCert) {
5495
ssl = { ca: databaseSSLRootCert }
5596
}
5697

57-
await connectAndMigrate(databaseUrl, './migrations/tenant', ssl)
98+
await connectAndMigrate(databaseUrl, './migrations/tenant', ssl, undefined, tenantId)
99+
100+
if (isMultitenant && tenantId) {
101+
await updateTenantMigrationVersion([tenantId])
102+
}
58103
}
59104

60105
/**
@@ -63,12 +108,14 @@ export async function runMigrationsOnTenant(databaseUrl: string): Promise<void>
63108
* @param migrationsDirectory
64109
* @param ssl
65110
* @param shouldCreateStorageSchema
111+
* @param tenantId
66112
*/
67113
async function connectAndMigrate(
68114
databaseUrl: string | undefined,
69115
migrationsDirectory: string,
70116
ssl?: ClientConfig['ssl'],
71-
shouldCreateStorageSchema?: boolean
117+
shouldCreateStorageSchema?: boolean,
118+
tenantId?: string
72119
) {
73120
const dbConfig: ClientConfig = {
74121
connectionString: databaseUrl,
@@ -78,6 +125,13 @@ async function connectAndMigrate(
78125
}
79126

80127
const client = new Client(dbConfig)
128+
client.on('error', (err) => {
129+
logSchema.error(logger, 'Error on database connection', {
130+
type: 'error',
131+
error: err,
132+
project: tenantId,
133+
})
134+
})
81135
try {
82136
await client.connect()
83137
await migrate({ client }, migrationsDirectory, shouldCreateStorageSchema)
@@ -114,6 +168,8 @@ function runMigrations(migrationsDirectory: string, shouldCreateStorageSchema =
114168
try {
115169
const migrationTableName = 'migrations'
116170

171+
await client.query(`SET search_path TO ${searchPath.join(',')}`)
172+
117173
let appliedMigrations: Migration[] = []
118174
if (await doesTableExist(client, migrationTableName)) {
119175
const { rows } = await client.query(`SELECT * FROM ${migrationTableName} ORDER BY id`)

src/database/tenant.ts

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { knex } from './multitenant-db'
44
import { StorageBackendError } from '../storage'
55
import { JwtPayload } from 'jsonwebtoken'
66
import { PubSubAdapter } from '../pubsub'
7-
import { RunMigrationsEvent } from '../queue/events/run-migrations'
87

98
interface TenantConfig {
109
anonKey?: string
@@ -26,14 +25,7 @@ export interface Features {
2625
}
2726
}
2827

29-
const {
30-
isMultitenant,
31-
dbServiceRole,
32-
serviceKey,
33-
jwtSecret,
34-
dbMigrationHash,
35-
dbDisableTenantMigrations,
36-
} = getConfig()
28+
const { isMultitenant, dbServiceRole, serviceKey, jwtSecret, dbMigrationHash } = getConfig()
3729

3830
const tenantConfigCache = new Map<string, TenantConfig>()
3931

@@ -80,42 +72,6 @@ export async function* listTenantsToMigrate() {
8072
}
8173
}
8274

83-
/**
84-
* Runs migrations for all tenants
85-
*/
86-
export async function runMigrations() {
87-
if (dbDisableTenantMigrations) {
88-
return
89-
}
90-
const result = await knex.raw(`SELECT pg_try_advisory_lock(?);`, ['-8575985245963000605'])
91-
const lockAcquired = result.rows.shift()?.pg_try_advisory_lock || false
92-
93-
if (!lockAcquired) {
94-
return
95-
}
96-
97-
try {
98-
const tenants = listTenantsToMigrate()
99-
for await (const tenantBatch of tenants) {
100-
await Promise.allSettled(
101-
tenantBatch.map((tenant) => {
102-
return RunMigrationsEvent.send({
103-
tenantId: tenant,
104-
singletonKey: tenant,
105-
tenant: {
106-
ref: tenant,
107-
},
108-
})
109-
})
110-
)
111-
}
112-
} finally {
113-
try {
114-
await knex.raw(`SELECT pg_advisory_unlock(?);`, ['-8575985245963000605'])
115-
} catch (e) {}
116-
}
117-
}
118-
11975
export function updateTenantMigrationVersion(tenantIds: string[]) {
12076
return knex
12177
.table('tenants')

src/http/routes/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export { default as bucket } from './bucket'
22
export { default as object } from './object'
33
export { default as render } from './render'
4-
export { default as tenant } from './tenant'
54
export { default as multiPart } from './tus'
65
export { default as healthcheck } from './health'
6+
export * from './tenant'

0 commit comments

Comments
 (0)