1import * as _ from 'lodash-es'2import fs from 'fs'3import { decomposeForkedKubeQs } from '../../serverF/dbF/decompForkedKubeQsF.mts'4import { remoteWipBackupDir, runPgOnKlust } from './backupDbConstantsF.mts'5import { chalkGreen } from '../../serverF/libChalkF.mts'6import { klusterCtx } from '../k8sF/ctxF/klusterCtxF.mts'7import { humanBytes } from '../../sharedF/strF/humanBytesF.mts'8import { kubeExecCat } from '../k8sF/kubeCpF.mts'9import { doKubeExec } from '../k8sF/kubeExecF.mts'10import { getSinglePod } from '../k8sF/getSinglePodF.mts'11import { cnpgPrimaryLabels } from '../k8sF/kubeLabelsF.mts'12import { pathDownJoin } from '../../serverF/pathF.mts'13import { liveSpawnThrow } from '../../serverF/fileutilsF/live1SpawnThrowF.mts'15import { assertDefined } from '../../sharedF/assertsF/assertDefinedF.mts'16import { runPgOnKlustCtx } from '../../serverF/dbF/runPgOnKlustCtxF.mts'17import { doKubeExecCapture } from '../k8sF/kubeExecF.mts'18import { maskQs } from '../../serverF/dbF/maskQsF.mts'20export const getDbPodInfo = async ({svcName, cluster_name, isCnPg}: {svcName: string, cluster_name: string, isCnPg?: boolean}) => {21 return getSinglePod({fuzzyPodName: svcName, cluster_name, labels: isCnPg ? cnpgPrimaryLabels(svcName) : undefined})22}24export const doBackupDb = async ({25 qsName, file_path, format, extraArgsS='', isQuiet,26 extraArgs2S=[]27}: {qsName: string, file_path: string, format: string, extraArgsS?: string, isQuiet?: boolean, extraArgs2S?: string[]}) => {28 runPgOnKlustCtx.enterWith({runPgOnKlust} as Record<string, unknown>)29 const {extQs, svcName, cluster_name, isCnPg} = await decomposeForkedKubeQs({qsName})30 klusterCtx.enterWith({...klusterCtx.getStore(), cluster_name})32 assertDefined(format)34 const getCmdA = (dumpFilePath: string) => {35 let retA = ['pg_dump', `--format=${format}`]36 if (extraArgsS) retA.push(extraArgsS)38 if (runPgOnKlust) {39 retA.push(extQs)40 } else {41 retA.push(`'${extQs}'`)42 }44 return [45 ...retA,46 ...extraArgs2S,47 `--file=${dumpFilePath}`,48 '-Upostgres',49 '--verbose',50 ]51 }53 let remoteSizeS = ''54 if (runPgOnKlust) {55 const podInfo = await getDbPodInfo({svcName, cluster_name, isCnPg})56 assertDefined(podInfo)57 const {podName, containerName} = podInfo58 const fromRemotePath = pathDownJoin(59 remoteWipBackupDir, _.last(_.split(file_path, '/'))!60 )62 await doKubeExec({cmdA: getCmdA(fromRemotePath), podName, containerName, isQuiet})63 remoteSizeS = await doKubeExecCapture({cmdA: ['stat', '-c', '%s', fromRemotePath], podName, containerName})64 try {65 await kubeExecCat({fromRemotePath, toLocalPath: file_path, podName, containerName, expectedSize: Number(remoteSizeS)})66 } finally {67 await doKubeExec({cmdA: ['rm', fromRemotePath], podName, containerName, isQuiet})68 }69 } else {70 const onDataFnc = (data: string): string | undefined => {71 if (isQuiet) return undefined72 const lastLine = data.split("\n")73 process.stdout.write('\n\r' + lastLine)74 return undefined75 }76 console.log('')77 await liveSpawnThrow({cmd: getCmdA(file_path).join(' '), printCmd: getCmdA(file_path).map(maskQs).join(' '), isQuiet, onDataFnc}) 78 }79 const {size} = fs.statSync(file_path)80 const remoteSize = Number(remoteSizeS)81 if (remoteSize && size !== remoteSize) {82 fs.unlinkSync(file_path)83 throw new Error(`backup size mismatch: local=${size} remote=${remoteSize} diff=${remoteSize - size}`)84 }85 console.log(chalkGreen(`backed up ` + humanBytes(size)), file_path.replace(process.env.HOME + '/', '~/'))86}