Merge branch 'feature/app-backups' of github.com:Budibase/budibase into feature/backups-ui
This commit is contained in:
commit
80842ed716
|
@ -4,8 +4,8 @@ import { publishEvent } from "../events"
|
||||||
export async function appBackupRestored(backup: AppBackup) {
|
export async function appBackupRestored(backup: AppBackup) {
|
||||||
const properties: AppBackupRevertEvent = {
|
const properties: AppBackupRevertEvent = {
|
||||||
appId: backup.appId,
|
appId: backup.appId,
|
||||||
backupName: backup.name,
|
backupName: backup.name!,
|
||||||
backupCreatedAt: backup.createdAt,
|
backupCreatedAt: backup.timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
await publishEvent(Event.APP_BACKUP_RESTORED, properties)
|
await publishEvent(Event.APP_BACKUP_RESTORED, properties)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { backups } from "@budibase/pro"
|
import { backups } from "@budibase/pro"
|
||||||
import { objectStore, tenancy, db as dbCore } from "@budibase/backend-core"
|
import { db as dbCore, objectStore, tenancy } from "@budibase/backend-core"
|
||||||
import { AppBackupQueueData } from "@budibase/types"
|
import { AppBackupQueueData, AppBackupStatus } from "@budibase/types"
|
||||||
import { exportApp } from "./exports"
|
import { exportApp } from "./exports"
|
||||||
import { importApp } from "./imports"
|
import { importApp } from "./imports"
|
||||||
|
import { calculateBackupStats } from "../statistics"
|
||||||
import { Job } from "bull"
|
import { Job } from "bull"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
|
@ -41,6 +42,11 @@ async function importProcessor(job: Job) {
|
||||||
await removeExistingApp(devAppId)
|
await removeExistingApp(devAppId)
|
||||||
await performImport(backupTarPath)
|
await performImport(backupTarPath)
|
||||||
}
|
}
|
||||||
|
await backups.updateRestoreStatus(
|
||||||
|
data.docId,
|
||||||
|
data.docRev,
|
||||||
|
AppBackupStatus.COMPLETE
|
||||||
|
)
|
||||||
fs.rmSync(backupTarPath)
|
fs.rmSync(backupTarPath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -52,28 +58,36 @@ async function exportProcessor(job: Job) {
|
||||||
name = data.export!.name || `${trigger} - backup`
|
name = data.export!.name || `${trigger} - backup`
|
||||||
const tenantId = tenancy.getTenantIDFromAppID(appId)
|
const tenantId = tenancy.getTenantIDFromAppID(appId)
|
||||||
await tenancy.doInTenant(tenantId, async () => {
|
await tenancy.doInTenant(tenantId, async () => {
|
||||||
const createdAt = new Date().toISOString()
|
const devAppId = dbCore.getDevAppID(appId),
|
||||||
const tarPath = await exportApp(appId, { tar: true })
|
prodAppId = dbCore.getProdAppID(appId)
|
||||||
let filename = `${appId}/backup-${createdAt}.tar.gz`
|
const timestamp = new Date().toISOString()
|
||||||
|
const tarPath = await exportApp(devAppId, { tar: true })
|
||||||
|
const contents = await calculateBackupStats(devAppId)
|
||||||
|
let filename = `${prodAppId}/backup-${timestamp}.tar.gz`
|
||||||
// add the tenant to the bucket path if backing up within a multi-tenant environment
|
// add the tenant to the bucket path if backing up within a multi-tenant environment
|
||||||
if (env.MULTI_TENANCY) {
|
if (env.MULTI_TENANCY) {
|
||||||
filename = `${tenantId}/${filename}`
|
filename = `${tenantId}/${filename}`
|
||||||
}
|
}
|
||||||
const bucket = objectStore.ObjectStoreBuckets.BACKUPS
|
const bucket = objectStore.ObjectStoreBuckets.BACKUPS
|
||||||
const metadata = {
|
|
||||||
appId,
|
|
||||||
createdAt,
|
|
||||||
trigger,
|
|
||||||
name,
|
|
||||||
}
|
|
||||||
await objectStore.upload({
|
await objectStore.upload({
|
||||||
path: tarPath,
|
path: tarPath,
|
||||||
type: "application/gzip",
|
type: "application/gzip",
|
||||||
bucket,
|
bucket,
|
||||||
filename,
|
filename,
|
||||||
metadata,
|
metadata: {
|
||||||
|
name,
|
||||||
|
trigger,
|
||||||
|
timestamp,
|
||||||
|
appId: prodAppId,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
await backups.storeAppBackupMetadata(filename, metadata)
|
await backups.updateBackupStatus(
|
||||||
|
data.docId,
|
||||||
|
data.docRev,
|
||||||
|
AppBackupStatus.COMPLETE,
|
||||||
|
contents,
|
||||||
|
filename
|
||||||
|
)
|
||||||
// clear up the tarball after uploading it
|
// clear up the tarball after uploading it
|
||||||
fs.rmSync(tarPath)
|
fs.rmSync(tarPath)
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
uploadDirectory,
|
uploadDirectory,
|
||||||
upload,
|
upload,
|
||||||
} from "../../../utilities/fileSystem/utilities"
|
} from "../../../utilities/fileSystem/utilities"
|
||||||
|
import { downloadTemplate } from "../../../utilities/fileSystem"
|
||||||
import { ObjectStoreBuckets, FieldTypes } from "../../../constants"
|
import { ObjectStoreBuckets, FieldTypes } from "../../../constants"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
|
@ -89,7 +90,7 @@ async function getTemplateStream(template: TemplateType) {
|
||||||
return fs.createReadStream(template.file.path)
|
return fs.createReadStream(template.file.path)
|
||||||
} else if (template.key) {
|
} else if (template.key) {
|
||||||
const [type, name] = template.key.split("/")
|
const [type, name] = template.key.split("/")
|
||||||
const tmpPath = await exports.downloadTemplate(type, name)
|
const tmpPath = await downloadTemplate(type, name)
|
||||||
return fs.createReadStream(join(tmpPath, name, "db", "dump.txt"))
|
return fs.createReadStream(join(tmpPath, name, "db", "dump.txt"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { context, db as dbCore } from "@budibase/backend-core"
|
||||||
|
import {
|
||||||
|
getDatasourceParams,
|
||||||
|
getTableParams,
|
||||||
|
getAutomationParams,
|
||||||
|
getScreenParams,
|
||||||
|
} from "../../../db/utils"
|
||||||
|
|
||||||
|
async function runInContext(appId: string, cb: any, db?: PouchDB.Database) {
|
||||||
|
if (db) {
|
||||||
|
return cb(db)
|
||||||
|
} else {
|
||||||
|
const devAppId = dbCore.getDevAppID(appId)
|
||||||
|
return context.doInAppContext(devAppId, () => {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
return cb(db)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function calculateDatasourceCount(
|
||||||
|
appId: string,
|
||||||
|
db?: PouchDB.Database
|
||||||
|
) {
|
||||||
|
return runInContext(
|
||||||
|
appId,
|
||||||
|
async (db: PouchDB.Database) => {
|
||||||
|
const datasourceList = await db.allDocs(getDatasourceParams())
|
||||||
|
const tableList = await db.allDocs(getTableParams())
|
||||||
|
return datasourceList.rows.length + tableList.rows.length
|
||||||
|
},
|
||||||
|
db
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function calculateAutomationCount(
|
||||||
|
appId: string,
|
||||||
|
db?: PouchDB.Database
|
||||||
|
) {
|
||||||
|
return runInContext(
|
||||||
|
appId,
|
||||||
|
async (db: PouchDB.Database) => {
|
||||||
|
const automationList = await db.allDocs(getAutomationParams())
|
||||||
|
return automationList.rows.length
|
||||||
|
},
|
||||||
|
db
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function calculateScreenCount(
|
||||||
|
appId: string,
|
||||||
|
db?: PouchDB.Database
|
||||||
|
) {
|
||||||
|
return runInContext(
|
||||||
|
appId,
|
||||||
|
async (db: PouchDB.Database) => {
|
||||||
|
const screenList = await db.allDocs(getScreenParams())
|
||||||
|
return screenList.rows.length
|
||||||
|
},
|
||||||
|
db
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function calculateBackupStats(appId: string) {
|
||||||
|
return runInContext(appId, async (db: PouchDB.Database) => {
|
||||||
|
const promises = []
|
||||||
|
promises.push(calculateDatasourceCount(appId, db))
|
||||||
|
promises.push(calculateAutomationCount(appId, db))
|
||||||
|
promises.push(calculateScreenCount(appId, db))
|
||||||
|
const responses = await Promise.all(promises)
|
||||||
|
return {
|
||||||
|
datasources: responses[0],
|
||||||
|
automations: responses[1],
|
||||||
|
screens: responses[2],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,29 +1,41 @@
|
||||||
import { Document } from "../document"
|
import { Document } from "../document"
|
||||||
|
|
||||||
|
export enum AppBackupType {
|
||||||
|
BACKUP = "backup",
|
||||||
|
RESTORE = "restore",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AppBackupStatus {
|
||||||
|
STARTED = "started",
|
||||||
|
COMPLETE = "complete",
|
||||||
|
FAILED = "failed",
|
||||||
|
}
|
||||||
|
|
||||||
export enum AppBackupTrigger {
|
export enum AppBackupTrigger {
|
||||||
PUBLISH = "publish",
|
PUBLISH = "publish",
|
||||||
MANUAL = "manual",
|
MANUAL = "manual",
|
||||||
SCHEDULED = "scheduled",
|
SCHEDULED = "scheduled",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppBackupEventType {
|
export interface AppBackupContents {
|
||||||
EXPORT = "export",
|
|
||||||
IMPORT = "import",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AppBackup extends Document {
|
|
||||||
trigger: AppBackupTrigger
|
|
||||||
name: string
|
|
||||||
createdAt: string
|
|
||||||
createdBy?: string
|
|
||||||
filename: string
|
|
||||||
appId: string
|
|
||||||
userId?: string
|
|
||||||
contents?: {
|
|
||||||
datasources: string[]
|
datasources: string[]
|
||||||
screens: string[]
|
screens: string[]
|
||||||
automations: string[]
|
automations: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AppBackupMetadata {
|
||||||
|
appId: string
|
||||||
|
trigger?: AppBackupTrigger
|
||||||
|
type: AppBackupType
|
||||||
|
status: AppBackupStatus
|
||||||
|
name?: string
|
||||||
|
createdBy?: string
|
||||||
|
timestamp: string
|
||||||
|
contents?: AppBackupContents
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppBackup extends Document, AppBackupMetadata {
|
||||||
|
filename?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppBackupFetchOpts = {
|
export type AppBackupFetchOpts = {
|
||||||
|
@ -36,8 +48,9 @@ export type AppBackupFetchOpts = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppBackupQueueData {
|
export interface AppBackupQueueData {
|
||||||
eventType: AppBackupEventType
|
|
||||||
appId: string
|
appId: string
|
||||||
|
docId: string
|
||||||
|
docRev: string
|
||||||
export?: {
|
export?: {
|
||||||
trigger: AppBackupTrigger
|
trigger: AppBackupTrigger
|
||||||
name?: string
|
name?: string
|
||||||
|
@ -45,13 +58,6 @@ export interface AppBackupQueueData {
|
||||||
}
|
}
|
||||||
import?: {
|
import?: {
|
||||||
backupId: string
|
backupId: string
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AppBackupMetadata {
|
|
||||||
appId: string
|
|
||||||
trigger: AppBackupTrigger
|
|
||||||
name?: string
|
|
||||||
createdBy?: string
|
createdBy?: string
|
||||||
createdAt: string
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue