Enhance backup command generation and logging for database backups

- Refactored backup utilities for PostgreSQL, MySQL, MariaDB, and MongoDB to streamline command generation and improve logging.
- Introduced a unified `getBackupCommand` function to encapsulate backup command logic, enhancing maintainability.
- Improved error handling and logging during backup processes, providing clearer feedback on command execution and success/failure states.
- Updated container retrieval methods to return null instead of throwing errors when no containers are found, improving robustness.
This commit is contained in:
Mauricio Siu
2025-05-04 00:15:09 -06:00
parent 557c89ac6d
commit 66dd890448
10 changed files with 219 additions and 290 deletions
+152 -8
View File
@@ -77,35 +77,179 @@ export const getS3Credentials = (destination: Destination) => {
};
export const getPostgresBackupCommand = (
containerId: string,
database: string,
databaseUser: string,
) => {
return `docker exec ${containerId} sh -c "pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip"`;
return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip"`;
};
export const getMariadbBackupCommand = (
containerId: string,
database: string,
databaseUser: string,
databasePassword: string,
) => {
return `docker exec ${containerId} sh -c "mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`;
return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`;
};
export const getMysqlBackupCommand = (
containerId: string,
database: string,
databasePassword: string,
) => {
return `docker exec ${containerId} sh -c "mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databasePassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip"`;
return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databasePassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip"`;
};
export const getMongoBackupCommand = (
containerId: string,
database: string,
databaseUser: string,
databasePassword: string,
) => {
return `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --archive --authenticationDatabase admin --gzip"`;
return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --archive --authenticationDatabase admin --gzip"`;
};
const getServiceContainerCommand = (appName: string) => {
return `docker ps -q --filter "status=running" --filter "label=com.docker.swarm.service.name=${appName}" | head -n 1`;
};
const getComposeContainerCommand = (
appName: string,
serviceName: string,
composeType: "stack" | "docker-compose" | undefined,
) => {
if (composeType === "stack") {
return `docker ps -q --filter "status=running" --filter "label=com.docker.stack.namespace=${appName}" --filter "label=com.docker.swarm.service.name=${serviceName}" | head -n 1`;
}
return `docker ps -q --filter "status=running" --filter "label=com.docker.compose.project=${appName}" --filter "label=com.docker.compose.service=${serviceName}" | head -n 1`;
};
const getContainerSearchCommand = (backup: BackupSchedule) => {
const { backupType, postgres, mysql, mariadb, mongo, compose, serviceName } =
backup;
if (backupType === "database") {
const appName =
postgres?.appName || mysql?.appName || mariadb?.appName || mongo?.appName;
return getServiceContainerCommand(appName || "");
}
if (backupType === "compose") {
const { appName, composeType } = compose || {};
return getComposeContainerCommand(
appName || "",
serviceName || "",
composeType,
);
}
};
export const generateBackupCommand = (backup: BackupSchedule) => {
const { backupType, databaseType } = backup;
switch (databaseType) {
case "postgres": {
const postgres = backup.postgres;
if (backupType === "database" && postgres) {
return getPostgresBackupCommand(backup.database, postgres.databaseUser);
}
if (backupType === "compose" && backup.metadata?.postgres) {
return getPostgresBackupCommand(
backup.database,
backup.metadata.postgres.databaseUser,
);
}
break;
}
case "mysql": {
const mysql = backup.mysql;
if (backupType === "database" && mysql) {
return getMysqlBackupCommand(backup.database, mysql.databasePassword);
}
if (backupType === "compose" && backup.metadata?.mysql) {
return getMysqlBackupCommand(
backup.database,
backup.metadata?.mysql?.databaseRootPassword || "",
);
}
break;
}
case "mariadb": {
const mariadb = backup.mariadb;
if (backupType === "database" && mariadb) {
return getMariadbBackupCommand(
backup.database,
mariadb.databaseUser,
mariadb.databasePassword,
);
}
if (backupType === "compose" && backup.metadata?.mariadb) {
return getMariadbBackupCommand(
backup.database,
backup.metadata.mariadb.databaseUser,
backup.metadata.mariadb.databasePassword,
);
}
break;
}
case "mongo": {
const mongo = backup.mongo;
if (backupType === "database" && mongo) {
return getMongoBackupCommand(
backup.database,
mongo.databaseUser,
mongo.databasePassword,
);
}
if (backupType === "compose" && backup.metadata?.mongo) {
return getMongoBackupCommand(
backup.database,
backup.metadata.mongo.databaseUser,
backup.metadata.mongo.databasePassword,
);
}
break;
}
default:
throw new Error(`Database type not supported: ${databaseType}`);
}
return null;
};
export const getBackupCommand = (
backup: BackupSchedule,
rcloneCommand: string,
logPath: string,
) => {
const containerSearch = getContainerSearchCommand(backup);
const backupCommand = generateBackupCommand(backup);
return `
set -eo pipefail;
echo "[$(date)] Starting backup process..." >> ${logPath};
echo "[$(date)] Executing backup command..." >> ${logPath};
CONTAINER_ID=$(${containerSearch})
if [ -z "$CONTAINER_ID" ]; then
echo "[$(date)] ❌ Container not found" >> ${logPath};
exit 1;
fi
echo "[$(date)] Container Up: $CONTAINER_ID" >> ${logPath};
# Run the backup command and capture the exit status
BACKUP_OUTPUT=$(${backupCommand} 2>&1 >/dev/null) || {
echo "[$(date)] ❌ backup failed" >> ${logPath};
echo "Error: $BACKUP_OUTPUT" >> ${logPath};
exit 1;
}
echo "[$(date)] ✅ backup completed successfully" >> ${logPath};
echo "[$(date)] Starting upload to S3..." >> ${logPath};
# Run the upload command and capture the exit status
UPLOAD_OUTPUT=$(${backupCommand} | ${rcloneCommand} 2>&1 >/dev/null) || {
echo "[$(date)] ❌ Upload to S3 failed" >> ${logPath};
echo "Error: $UPLOAD_OUTPUT" >> ${logPath};
exit 1;
}
echo "[$(date)] ✅ Upload to S3 completed successfully" >> ${logPath};
echo "Backup done ✅" >> ${logPath};
`;
};