Workspace Versioning

Closes #857

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2022-01-25 01:48:43 +07:00
parent ed1550839d
commit 7d1df3c36d
36 changed files with 502 additions and 210 deletions
+13 -38
View File
@@ -14,7 +14,7 @@
// limitations under the License.
//
import accountPlugin, {
import {
ACCOUNT_DB,
assignWorkspace,
createAccount,
@@ -22,23 +22,20 @@ import accountPlugin, {
dropAccount,
dropWorkspace,
getAccount,
listAccounts,
listWorkspaces,
listAccounts
upgradeWorkspace
} from '@anticrm/account'
import { setMetadata } from '@anticrm/platform'
import toolPlugin, { prepareTools, version } from '@anticrm/server-tool'
import { program } from 'commander'
import { Client } from 'minio'
import { Db, MongoClient } from 'mongodb'
import { rebuildElastic } from './elastic'
import { importXml } from './importer'
import { clearTelegramHistory } from './telegram'
import { diffWorkspace, dumpWorkspace, restoreWorkspace, upgradeWorkspace } from './workspace'
import { diffWorkspace, dumpWorkspace, restoreWorkspace } from './workspace'
const mongodbUri = process.env.MONGO_URL
if (mongodbUri === undefined) {
console.error('please provide mongodb url.')
process.exit(1)
}
const { mongodbUri, minio } = prepareTools()
const transactorUrl = process.env.TRANSACTOR_URL
if (transactorUrl === undefined) {
@@ -46,40 +43,14 @@ if (transactorUrl === undefined) {
process.exit(1)
}
const minioEndpoint = process.env.MINIO_ENDPOINT
if (minioEndpoint === undefined) {
console.error('please provide minio endpoint')
process.exit(1)
}
const minioAccessKey = process.env.MINIO_ACCESS_KEY
if (minioAccessKey === undefined) {
console.error('please provide minio access key')
process.exit(1)
}
const minioSecretKey = process.env.MINIO_SECRET_KEY
if (minioSecretKey === undefined) {
console.error('please provide minio secret key')
process.exit(1)
}
const elasticUrl = process.env.ELASTIC_URL
if (elasticUrl === undefined) {
console.error('please provide elastic url')
process.exit(1)
}
setMetadata(accountPlugin.metadata.Endpoint, transactorUrl)
setMetadata(accountPlugin.metadata.Transactor, transactorUrl)
const minio = new Client({
endPoint: minioEndpoint,
port: 9000,
useSSL: false,
accessKey: minioAccessKey,
secretKey: minioSecretKey
})
setMetadata(toolPlugin.metadata.Endpoint, transactorUrl)
setMetadata(toolPlugin.metadata.Transactor, transactorUrl)
async function withDatabase (uri: string, f: (db: Db, client: MongoClient) => Promise<any>): Promise<void> {
console.log(`connecting to database '${uri}'...`)
@@ -139,7 +110,9 @@ program
.command('upgrade-workspace <name>')
.description('upgrade workspace')
.action(async (workspace, cmd) => {
await upgradeWorkspace(mongodbUri, workspace, transactorUrl, minio)
return await withDatabase(mongodbUri, async (db) => {
await upgradeWorkspace(db, workspace)
})
})
program
@@ -158,6 +131,8 @@ program
return await withDatabase(mongodbUri, async (db) => {
const workspacesJSON = JSON.stringify(await listWorkspaces(db), null, 2)
console.info(workspacesJSON)
console.log('latest model version:', JSON.stringify(version))
})
})
-98
View File
@@ -1,98 +0,0 @@
import {
Doc,
DocumentQuery,
Domain,
FindOptions,
isOperator, Ref, SortingOrder
} from '@anticrm/core'
import { MigrationClient, MigrateUpdate, MigrationResult } from '@anticrm/model'
import { Db, Document, Filter, Sort, UpdateFilter } from 'mongodb'
/**
* Upgrade client implementation.
*/
export class MigrateClientImpl implements MigrationClient {
constructor (readonly db: Db) {
}
private translateQuery<T extends Doc>(query: DocumentQuery<T>): Filter<Document> {
const translated: any = {}
for (const key in query) {
const value = (query as any)[key]
if (value !== null && typeof value === 'object') {
const keys = Object.keys(value)
if (keys[0] === '$like') {
const pattern = value.$like as string
translated[key] = {
$regex: `^${pattern.split('%').join('.*')}$`,
$options: 'i'
}
continue
}
}
translated[key] = value
}
return translated
}
async find<T extends Doc>(
domain: Domain,
query: DocumentQuery<T>,
options?: FindOptions<T> | undefined
): Promise<T[]> {
let cursor = this.db.collection(domain).find<T>(this.translateQuery(query))
if (options?.limit !== undefined) {
cursor = cursor.limit(options.limit)
}
if (options !== null && options !== undefined) {
if (options.sort !== undefined) {
const sort: Sort = {}
for (const key in options.sort) {
const order = options.sort[key] === SortingOrder.Ascending ? 1 : -1
sort[key] = order
}
cursor = cursor.sort(sort)
}
}
return await cursor.toArray()
}
async update<T extends Doc>(domain: Domain, query: DocumentQuery<T>, operations: MigrateUpdate<T>): Promise<MigrationResult> {
if (isOperator(operations)) {
const result = await this.db
.collection(domain)
.updateMany(this.translateQuery(query), { ...operations } as unknown as UpdateFilter<any>)
return { matched: result.matchedCount, updated: result.modifiedCount }
} else {
const result = await this.db.collection(domain).updateMany(this.translateQuery(query), { $set: operations })
return { matched: result.matchedCount, updated: result.modifiedCount }
}
}
async move <T extends Doc>(sourceDomain: Domain, query: DocumentQuery<T>, targetDomain: Domain): Promise<MigrationResult> {
const q = this.translateQuery(query)
const cursor = this.db.collection(sourceDomain).find<T>(q)
const target = this.db.collection(targetDomain)
const result: MigrationResult = {
matched: 0,
updated: 0
}
let doc: Document | null
while ((doc = await cursor.next()) != null) {
await target.insertOne(doc)
result.matched++
result.updated++
}
await this.db.collection(sourceDomain).deleteMany(q)
return result
}
async create <T extends Doc>(domain: Domain, doc: T): Promise<void> {
await this.db.collection(domain).insertOne(doc as Document)
}
async delete <T extends Doc>(domain: Domain, _id: Ref<T>): Promise<void> {
await this.db.collection(domain).deleteOne({ _id })
}
}
+5 -59
View File
@@ -16,70 +16,16 @@
import contact from '@anticrm/contact'
import core, { DOMAIN_TX, Tx } from '@anticrm/core'
import builder, { migrateOperations } from '@anticrm/model-all'
import builder, { version } from '@anticrm/model-all'
import { upgradeModel } from '@anticrm/server-tool'
import { existsSync } from 'fs'
import { mkdir, open, readFile, writeFile } from 'fs/promises'
import { Client } from 'minio'
import { Document, MongoClient } from 'mongodb'
import { join } from 'path'
import { connect } from './connect'
import { MigrateClientImpl } from './upgrade'
import { rebuildElastic } from './elastic'
import { generateModelDiff, printDiff } from './mdiff'
import { listMinioObjects, MinioWorkspaceItem } from './minio'
import { rebuildElastic } from './elastic'
const txes = JSON.parse(JSON.stringify(builder.getTxes())) as Tx[]
/**
* @public
*/
export async function upgradeWorkspace (
mongoUrl: string,
dbName: string,
transactorUrl: string,
minio: Client
): Promise<void> {
if (txes.some((tx) => tx.objectSpace !== core.space.Model)) {
throw Error('Model txes must target only core.space.Model')
}
const client = new MongoClient(mongoUrl)
try {
await client.connect()
const db = client.db(dbName)
console.log('removing model...')
// we're preserving accounts (created by core.account.System).
const result = await db.collection(DOMAIN_TX).deleteMany({
objectSpace: core.space.Model,
modifiedBy: core.account.System,
objectClass: { $ne: contact.class.EmployeeAccount }
})
console.log(`${result.deletedCount} transactions deleted.`)
console.log('creating model...')
const model = txes
const insert = await db.collection(DOMAIN_TX).insertMany(model as Document[])
console.log(`${insert.insertedCount} model transactions inserted.`)
const migrateClient = new MigrateClientImpl(db)
for (const op of migrateOperations) {
await op.migrate(migrateClient)
}
console.log('Apply upgrade operations')
const connection = await connect(transactorUrl, dbName)
for (const op of migrateOperations) {
await op.upgrade(connection)
}
await connection.close()
} finally {
await client.close()
}
}
interface CollectionInfo {
name: string
file: string
@@ -107,7 +53,7 @@ export async function dumpWorkspace (mongoUrl: string, dbName: string, fileName:
}
const workspaceInfo: WorkspaceInfo = {
version: '0.6.0',
version: `${version.major}.${version.minor}.${version.patch}`,
collections: [],
minioData: []
}
@@ -215,7 +161,7 @@ export async function restoreWorkspace (
}
}
await upgradeWorkspace(mongoUrl, dbName, transactorUrl, minio)
await upgradeModel(dbName, transactorUrl)
await rebuildElastic(mongoUrl, dbName, minio, elasticUrl)
} finally {