chore: Reduce lint exceptions in ctl (#37643)

Fix linting exceptions in `ctl`.


## Automation

/test sanity

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!WARNING]
> Tests have not run on the HEAD
1f2242abcfac193fb321dee8d64cb194dea0f803 yet
> <hr>Fri, 22 Nov 2024 10:06:23 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

- **New Features**
- Enhanced backup and restore processes with improved user prompts and
error handling.
- Added support for optional command-line flags during database imports.

- **Bug Fixes**
- Improved error handling for various operations, including database
exports and imports.
	- Enhanced logging for backup errors to provide more context.

- **Documentation**
- Updated user prompts and error messages for clarity during backup and
restore operations.

- **Tests**
- Expanded test coverage for backup functionalities and utility
functions to ensure robust error handling and output validation.

- **Chores**
	- Updated dependencies to enhance TypeScript development experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Shrikant Sharat Kandula 2024-11-22 15:57:56 +05:30 committed by GitHub
parent d87f7ccd62
commit 0685d335ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 145 additions and 25 deletions

View File

@ -36,6 +36,7 @@
"devDependencies": {
"@types/express": "^4.17.14",
"@types/jest": "^29.2.3",
"@types/node": "*",
"@types/nodemailer": "^6.4.17",
"@types/readline-sync": "^1.4.8",
"jest": "^29.3.1",

View File

@ -1,16 +1,6 @@
{
"extends": ["../../../../.eslintrc.base.json"],
"extends": ["../../.eslintrc.json"],
"rules": {
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/prefer-nullish-coalescing": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/no-explicit-any": "off",
"testing-library/no-debugging-utils": "off",
"@typescript-eslint/no-var-requires": "off",
"padding-line-between-statements": "off",
"no-console": "off",
"@typescript-eslint/promise-function-async": "off",
"@typescript-eslint/no-unused-vars": "off",
"sort-destructure-keys/sort-destructure-keys": "off"
"no-console": "off"
}
}

View File

@ -6,7 +6,6 @@ jest.mock("./utils", () => ({
import * as backup from "./backup";
import * as Constants from "./constants";
import os from "os";
// @ts-ignore
import fsPromises from "fs/promises";
import * as utils from "./utils";
import readlineSync from "readline-sync";
@ -21,16 +20,19 @@ describe("Backup Tests", () => {
test("Available Space in /appsmith-stacks volume in Bytes", async () => {
const res = expect(await backup.getAvailableBackupSpaceInBytes("/"));
res.toBeGreaterThan(1024 * 1024);
});
it("Check the constant is 2 GB", () => {
const size = 2 * 1024 * 1024 * 1024;
expect(Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES).toBe(size);
});
it("Should throw Error when the available size is below MIN_REQUIRED_DISK_SPACE_IN_BYTES", () => {
const size = Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES - 1;
expect(() => backup.checkAvailableBackupSpace(size)).toThrow();
});
@ -48,12 +50,14 @@ describe("Backup Tests", () => {
os.tmpdir = jest.fn().mockReturnValue("temp/dir");
fsPromises.mkdtemp = jest.fn().mockImplementation((a) => a);
const res = await backup.generateBackupRootPath();
expect(res).toBe("temp/dir/appsmithctl-backup-");
});
test("Test backup contents path generation", () => {
const root = "/rootDir";
const timestamp = "0000-00-0T00-00-00.00Z";
expect(backup.getBackupContentsPath(root, timestamp)).toBe(
"/rootDir/appsmith-backup-0000-00-0T00-00-00.00Z",
);
@ -65,6 +69,7 @@ describe("Backup Tests", () => {
const cmd =
"mongodump --uri=mongodb://username:password@host/appsmith --archive=/dest/mongodb-data.gz --gzip";
const res = await backup.executeMongoDumpCMD(dest, appsmithMongoURI);
expect(res).toBe(cmd);
console.log(res);
});
@ -86,6 +91,7 @@ describe("Backup Tests", () => {
const dest = "/destdir";
const cmd = "ln -s /appsmith-stacks/git-storage /destdir/git-storage";
const res = await backup.executeCopyCMD(gitRoot, dest);
expect(res).toBe(cmd);
console.log(res);
});
@ -99,6 +105,7 @@ describe("Backup Tests", () => {
}
});
const res = await utils.getCurrentAppsmithVersion();
expect(res).toBe("v0.0.0-SNAPSHOT");
});
@ -130,10 +137,12 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 4 and there are 5 files", async () => {
const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1", "file2", "file3", "file4", "file5"];
const expectedBackupFiles = ["file2", "file3", "file4", "file5"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res);
expect(res).toEqual(expectedBackupFiles);
@ -141,10 +150,12 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 2 and there are 5 files", async () => {
const backupArchivesLimit = 2;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1", "file2", "file3", "file4", "file5"];
const expectedBackupFiles = ["file4", "file5"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res);
expect(res).toEqual(expectedBackupFiles);
@ -152,10 +163,12 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 4 and there are 4 files", async () => {
const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1", "file2", "file3", "file4"];
const expectedBackupFiles = ["file1", "file2", "file3", "file4"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res);
expect(res).toEqual(expectedBackupFiles);
@ -163,10 +176,12 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 4 and there are 2 files", async () => {
const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1", "file2"];
const expectedBackupFiles = ["file1", "file2"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res);
expect(res).toEqual(expectedBackupFiles);
@ -174,26 +189,31 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 2 and there is 1 file", async () => {
const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1"];
const expectedBackupFiles = ["file1"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res);
expect(res).toEqual(expectedBackupFiles);
});
test("Cleanup Backups when limit is 2 and there is no file", async () => {
const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = [];
const expectedBackupFiles = [];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res);
expect(res).toEqual(expectedBackupFiles);
});
test("Test get encryption password from user prompt when both passwords are the same", async () => {
const password = "password#4321";
readlineSync.question = jest.fn().mockImplementation(() => password);
const password_res = backup.getEncryptionPasswordFromUser();
@ -202,10 +222,12 @@ describe("Backup Tests", () => {
test("Test get encryption password from user prompt when both passwords are the different", async () => {
const password = "password#4321";
readlineSync.question = jest.fn().mockImplementation((a) => {
if (a == "Enter the above password again: ") {
return "pass";
}
return password;
});
const password_res = backup.getEncryptionPasswordFromUser();
@ -233,6 +255,7 @@ describe("Backup Tests", () => {
archivePath,
encryptionPassword,
);
console.log(res);
expect(res).toEqual("/rootDir/appsmith-backup-0000-00-0T00-00-00.00Z.enc");
});
@ -243,6 +266,7 @@ test("Get DB name from Mongo URI 1", async () => {
"mongodb+srv://admin:password@test.cluster.mongodb.net/my_db_name?retryWrites=true&minPoolSize=1&maxPoolSize=10&maxIdleTimeMS=900000&authSource=admin";
const expectedDBName = "my_db_name";
const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri);
expect(dbName).toEqual(expectedDBName);
});
@ -251,6 +275,7 @@ test("Get DB name from Mongo URI 2", async () => {
"mongodb+srv://admin:password@test.cluster.mongodb.net/test123?retryWrites=true&minPoolSize=1&maxPoolSize=10&maxIdleTimeMS=900000&authSource=admin";
const expectedDBName = "test123";
const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri);
expect(dbName).toEqual(expectedDBName);
});
@ -259,6 +284,7 @@ test("Get DB name from Mongo URI 3", async () => {
"mongodb+srv://admin:password@test.cluster.mongodb.net/test123";
const expectedDBName = "test123";
const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri);
expect(dbName).toEqual(expectedDBName);
});
@ -266,5 +292,6 @@ test("Get DB name from Mongo URI 4", async () => {
const mongodb_uri = "mongodb://appsmith:pAssW0rd!@localhost:27017/appsmith";
const expectedDBName = "appsmith";
const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri);
expect(dbName).toEqual(expectedDBName);
});

View File

@ -1,4 +1,3 @@
// @ts-ignore
import fsPromises from "fs/promises";
import path from "path";
import os from "os";
@ -23,6 +22,7 @@ export async function run() {
console.log("Available free space at /appsmith-stacks");
const availSpaceInBytes =
getAvailableBackupSpaceInBytes("/appsmith-stacks");
console.log("\n");
checkAvailableBackupSpace(availSpaceInBytes);
@ -43,26 +43,32 @@ export async function run() {
tty.isatty((process.stdout as any).fd)
) {
encryptionPassword = getEncryptionPasswordFromUser();
if (encryptionPassword == -1) {
throw new Error(
"Backup process aborted because a valid enctyption password could not be obtained from the user",
);
}
encryptArchive = true;
}
await exportDockerEnvFile(backupContentsPath, encryptArchive);
archivePath = await createFinalArchive(backupRootPath, timestamp);
// shell.exec("openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -in " + archivePath + " -out " + archivePath + ".enc");
if (encryptArchive) {
const encryptedArchivePath = await encryptBackupArchive(
archivePath,
encryptionPassword,
);
await logger.backup_info(
"Finished creating an encrypted a backup archive at " +
encryptedArchivePath,
);
if (archivePath != null) {
await fsPromises.rm(archivePath, { recursive: true, force: true });
}
@ -94,6 +100,7 @@ export async function run() {
if (command_args.includes("--error-mail")) {
const currentTS = new Date().getTime();
const lastMailTS = await utils.getLastBackupErrorMailSentInMilliSec();
if (
lastMailTS +
Constants.DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC <
@ -107,11 +114,13 @@ export async function run() {
if (backupRootPath != null) {
await fsPromises.rm(backupRootPath, { recursive: true, force: true });
}
if (encryptArchive) {
if (archivePath != null) {
await fsPromises.rm(archivePath, { recursive: true, force: true });
}
}
await postBackupCleanup();
process.exit(errorCode);
}
@ -119,6 +128,7 @@ export async function run() {
export async function encryptBackupArchive(archivePath, encryptionPassword) {
const encryptedArchivePath = archivePath + ".enc";
await utils.execCommand([
"openssl",
"enc",
@ -133,11 +143,16 @@ export async function encryptBackupArchive(archivePath, encryptionPassword) {
"-k",
encryptionPassword,
]);
return encryptedArchivePath;
}
export function getEncryptionPasswordFromUser() {
for (const _ of [1, 2, 3]) {
for (const attempt of [1, 2, 3]) {
if (attempt > 1) {
console.log("Retry attempt", attempt);
}
const encryptionPwd1 = readlineSync.question(
"Enter a password to encrypt the backup archive: ",
{ hideEchoBack: true },
@ -146,10 +161,12 @@ export function getEncryptionPasswordFromUser() {
"Enter the above password again: ",
{ hideEchoBack: true },
);
if (encryptionPwd1 === encryptionPwd2) {
if (encryptionPwd1) {
return encryptionPwd1;
}
console.error(
"Invalid input. Empty password is not allowed, please try again.",
);
@ -157,9 +174,11 @@ export function getEncryptionPasswordFromUser() {
console.error("The passwords do not match, please try again.");
}
}
console.error(
"Aborting backup process, failed to obtain valid encryption password.",
);
return -1;
}
@ -185,6 +204,7 @@ async function createManifestFile(path) {
appsmithVersion: version,
dbName: utils.getDatabaseNameFromMongoURI(utils.getDburl()),
};
await fsPromises.writeFile(
path + "/manifest.json",
JSON.stringify(manifest_data),
@ -198,6 +218,7 @@ async function exportDockerEnvFile(destFolder, encryptArchive) {
{ encoding: "utf8" },
);
let cleaned_content = removeSensitiveEnvData(content);
if (encryptArchive) {
cleaned_content +=
"\nAPPSMITH_ENCRYPTION_SALT=" +
@ -205,6 +226,7 @@ async function exportDockerEnvFile(destFolder, encryptArchive) {
"\nAPPSMITH_ENCRYPTION_PASSWORD=" +
process.env.APPSMITH_ENCRYPTION_PASSWORD;
}
await fsPromises.writeFile(destFolder + "/docker.env", cleaned_content);
console.log("Exporting docker environment file done.");
}
@ -222,6 +244,7 @@ async function createFinalArchive(destFolder, timestamp) {
console.log("Creating final archive");
const archive = `${Constants.BACKUP_PATH}/appsmith-backup-${timestamp}.tar.gz`;
await utils.execCommand([
"tar",
"-cah",
@ -243,10 +266,13 @@ async function postBackupCleanup() {
process.env.APPSMITH_BACKUP_ARCHIVE_LIMIT,
);
const backupFiles = await utils.listLocalBackupFiles();
while (backupFiles.length > backupArchivesLimit) {
const fileName = backupFiles.shift();
await fsPromises.rm(Constants.BACKUP_PATH + "/" + fileName);
}
console.log("Cleanup task completed.");
}
@ -263,10 +289,11 @@ export function getGitRoot(gitRoot?) {
if (gitRoot == null || gitRoot === "") {
gitRoot = "/appsmith-stacks/git-storage";
}
return gitRoot;
}
export function generateBackupRootPath() {
export async function generateBackupRootPath() {
return fsPromises.mkdtemp(path.join(os.tmpdir(), "appsmithctl-backup-"));
}
@ -277,6 +304,7 @@ export function getBackupContentsPath(backupRootPath, timestamp) {
export function removeSensitiveEnvData(content) {
// Remove encryption and Mongodb data from docker.env
const output_lines = [];
content.split(/\r?\n/).forEach((line) => {
if (
!line.startsWith("APPSMITH_ENCRYPTION") &&
@ -286,20 +314,24 @@ export function removeSensitiveEnvData(content) {
output_lines.push(line);
}
});
return output_lines.join("\n");
}
export function getBackupArchiveLimit(backupArchivesLimit?) {
if (!backupArchivesLimit)
backupArchivesLimit = Constants.APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT;
return backupArchivesLimit;
}
export async function removeOldBackups(backupFiles, backupArchivesLimit) {
while (backupFiles.length > backupArchivesLimit) {
const fileName = backupFiles.shift();
await fsPromises.rm(Constants.BACKUP_PATH + "/" + fileName);
}
return backupFiles;
}
@ -309,6 +341,7 @@ export function getTimeStampInISO() {
export async function getAvailableBackupSpaceInBytes(path) {
const stat = await fsPromises.statfs(path);
return stat.bsize * stat.bfree;
}

View File

@ -26,6 +26,7 @@ export async function exec() {
async function checkReplicaSet(client: MongoClient) {
await client.connect();
return await new Promise<boolean>((resolve) => {
try {
const changeStream = client
@ -43,6 +44,7 @@ async function checkReplicaSet(client: MongoClient) {
} else {
console.error("Error even from changeStream", err);
}
resolve(false);
});

View File

@ -1,4 +1,3 @@
// @ts-ignore
import fsPromises from "fs/promises";
import * as Constants from "./constants";
import * as utils from "./utils";
@ -6,6 +5,7 @@ import * as utils from "./utils";
export async function exportDatabase() {
console.log("export_database ....");
const dbUrl = utils.getDburl();
await fsPromises.mkdir(Constants.BACKUP_PATH, { recursive: true });
await utils.execCommand([
"mongodump",

View File

@ -30,6 +30,7 @@ export async function run(forceOption) {
console.log("stop backend & rts application before import database");
await utils.stop(["backend", "rts"]);
let shellCmdResult: string;
try {
shellCmdResult = await utils.execCommandReturningOutput([
"mongo",
@ -43,11 +44,14 @@ export async function run(forceOption) {
throw error;
}
const collectionsLen = parseInt(shellCmdResult.trimEnd());
if (collectionsLen > 0) {
if (forceOption) {
await importDatabase();
return;
}
console.log();
console.log(
"**************************** WARNING ****************************",
@ -59,12 +63,15 @@ export async function run(forceOption) {
"Importing this DB will erase this data. Are you sure you want to proceed?[Yes/No] ",
);
const answer = input && input.toLocaleUpperCase();
if (answer === "Y" || answer === "YES") {
await importDatabase();
return;
} else if (answer === "N" || answer === "NO") {
return;
}
console.log(
`Your input is invalid. Please try to run import command again.`,
);

View File

@ -38,6 +38,7 @@ if (["export-db", "export_db", "ex"].includes(command)) {
console.log("Importing database");
// Get Force option flag to run import DB immediately
const forceOption = process.argv[3] === "-f";
try {
import_db.run(forceOption);
console.log("Importing database done");

View File

@ -1,4 +1,3 @@
// @ts-ignore
import fsPromises from "fs/promises";
import * as Constants from "./constants";

View File

@ -59,6 +59,7 @@ export async function sendBackupErrorToAdmins(err, backupTimestamp) {
if (instanceName) {
text = text + "Appsmith instance name: " + instanceName + "\n";
}
if (domainName) {
text =
text +
@ -68,6 +69,7 @@ export async function sendBackupErrorToAdmins(err, backupTimestamp) {
"/settings/general" +
"\n";
}
text = text + "\n" + err.stack;
const transporter = nodemailer.createTransport({

View File

@ -4,6 +4,7 @@ const command_args = process.argv.slice(3);
export async function exec() {
let errorCode = 0;
try {
await execMongoEval(command_args[0], process.env.APPSMITH_DB_URL);
} catch (err) {
@ -16,9 +17,11 @@ export async function exec() {
async function execMongoEval(queryExpression, appsmithMongoURI) {
queryExpression = queryExpression.trim();
if (command_args.includes("--pretty")) {
queryExpression += ".pretty()";
}
return await utils.execCommand([
"mongosh",
appsmithMongoURI,

View File

@ -1,4 +1,3 @@
// @ts-ignore
import fsPromises from "fs/promises";
import path from "path";
import os from "os";
@ -10,14 +9,17 @@ const command_args = process.argv.slice(3);
async function getBackupFileName() {
const backupFiles = await utils.listLocalBackupFiles();
console.log(
"\n" +
backupFiles.length +
" Appsmith backup file(s) found: [Sorted in ascending/chronological order]",
);
if (backupFiles.length == 0) {
return;
}
console.log(
"----------------------------------------------------------------",
);
@ -25,11 +27,13 @@ async function getBackupFileName() {
console.log(
"----------------------------------------------------------------",
);
for (let i = 0; i < backupFiles.length; i++) {
if (i === backupFiles.length - 1)
console.log(i + "\t|\t" + backupFiles[i] + " <--Most recent backup");
else console.log(i + "\t|\t" + backupFiles[i]);
}
console.log(
"----------------------------------------------------------------",
);
@ -38,6 +42,7 @@ async function getBackupFileName() {
readlineSync.question("Please enter the backup file index: "),
10,
);
if (
!isNaN(backupFileIndex) &&
Number.isInteger(backupFileIndex) &&
@ -54,8 +59,14 @@ async function getBackupFileName() {
async function decryptArchive(encryptedFilePath, backupFilePath) {
console.log("Enter the password to decrypt the backup archive:");
for (const _ of [1, 2, 3]) {
for (const attempt of [1, 2, 3]) {
if (attempt > 1) {
console.log("Retry attempt", attempt);
}
const decryptionPwd = readlineSync.question("", { hideEchoBack: true });
try {
await utils.execCommandSilent([
"openssl",
@ -72,11 +83,13 @@ async function decryptArchive(encryptedFilePath, backupFilePath) {
"-k",
decryptionPwd,
]);
return true;
} catch (error) {
console.log("Invalid password. Please try again:");
}
}
return false;
}
@ -101,9 +114,11 @@ async function restoreDatabase(restoreContentsPath, dbUrl) {
`--archive=${restoreContentsPath}/mongodb-data.gz`,
"--gzip",
];
try {
const fromDbName = await getBackupDatabaseName(restoreContentsPath);
const toDbName = utils.getDatabaseNameFromMongoURI(dbUrl);
console.log("Restoring database from " + fromDbName + " to " + toDbName);
cmd.push(
"--nsInclude=*",
@ -130,6 +145,7 @@ async function restoreDockerEnvFile(
const updatedbUrl = utils.getDburl();
let encryptionPwd = process.env.APPSMITH_ENCRYPTION_PASSWORD;
let encryptionSalt = process.env.APPSMITH_ENCRYPTION_SALT;
await utils.execCommand([
"mv",
dockerEnvFile,
@ -140,6 +156,7 @@ async function restoreDockerEnvFile(
restoreContentsPath + "/docker.env",
dockerEnvFile,
]);
if (overwriteEncryptionKeys) {
if (encryptionPwd && encryptionSalt) {
const input = readlineSync.question(
@ -148,6 +165,7 @@ async function restoreDockerEnvFile(
Or Type "n"/"No" to provide encryption key & password corresponding to the original Appsmith instance that is being restored.\n',
);
const answer = input && input.toLocaleUpperCase();
if (answer === "N" || answer === "NO") {
encryptionPwd = readlineSync.question(
"Enter the APPSMITH_ENCRYPTION_PASSWORD: ",
@ -180,6 +198,7 @@ async function restoreDockerEnvFile(
},
);
}
await fsPromises.appendFile(
dockerEnvFile,
"\nAPPSMITH_ENCRYPTION_PASSWORD=" +
@ -204,6 +223,7 @@ async function restoreDockerEnvFile(
process.env.APPSMITH_MONGODB_PASSWORD,
);
}
console.log("Restoring docker environment file completed");
}
@ -211,6 +231,7 @@ async function restoreGitStorageArchive(restoreContentsPath, backupName) {
console.log("Restoring git-storage archive");
// TODO: Consider APPSMITH_GIT_ROOT env for later iterations
const gitRoot = "/appsmith-stacks/git-storage";
await utils.execCommand(["mv", gitRoot, gitRoot + "-" + backupName]);
await utils.execCommand([
"mv",
@ -228,6 +249,7 @@ async function checkRestoreVersionCompatability(restoreContentsPath) {
);
const manifest_json = JSON.parse(manifest_data);
const restoreVersion = manifest_json["appsmithVersion"];
console.log("Current Appsmith Version: " + currentVersion);
console.log("Restore Appsmith Version: " + restoreVersion);
@ -251,6 +273,7 @@ async function checkRestoreVersionCompatability(restoreContentsPath) {
const confirm = readlineSync.question(
'Press Enter to continue \nOr Type "c" to cancel the restore process.\n',
);
if (confirm.toLowerCase() === "c") {
process.exit(0);
}
@ -259,6 +282,7 @@ async function checkRestoreVersionCompatability(restoreContentsPath) {
async function getBackupDatabaseName(restoreContentsPath) {
let db_name = "appsmith";
if (command_args.includes("--backup-db-name")) {
for (let i = 0; i < command_args.length; i++) {
if (command_args[i].startsWith("--backup-db-name")) {
@ -271,12 +295,14 @@ async function getBackupDatabaseName(restoreContentsPath) {
{ encoding: "utf8" },
);
const manifest_json = JSON.parse(manifest_data);
if ("dbName" in manifest_json) {
db_name = manifest_json["dbName"];
}
}
console.log("Backup Database Name: " + db_name);
return db_name;
}
@ -290,15 +316,18 @@ export async function run() {
try {
let backupFileName = await getBackupFileName();
if (backupFileName == null) {
process.exit(errorCode);
} else {
backupFilePath = path.join(Constants.BACKUP_PATH, backupFileName);
if (isArchiveEncrypted(backupFileName)) {
const encryptedBackupFilePath = path.join(
Constants.BACKUP_PATH,
backupFileName,
);
backupFileName = backupFileName.replace(".enc", "");
backupFilePath = path.join(Constants.BACKUP_PATH, backupFileName);
cleanupArchive = true;
@ -307,6 +336,7 @@ export async function run() {
encryptedBackupFilePath,
backupFilePath,
);
if (!decryptSuccess) {
console.log(
"You have entered the incorrect password multiple times. Aborting the restore process.",
@ -315,6 +345,7 @@ export async function run() {
process.exit(errorCode);
}
}
const backupName = backupFileName.replace(/\.tar\.gz$/, "");
const restoreRootPath = await fsPromises.mkdtemp(os.tmpdir());
const restoreContentsPath = path.join(restoreRootPath, backupName);
@ -346,6 +377,7 @@ export async function run() {
if (cleanupArchive) {
await fsPromises.rm(backupFilePath, { force: true });
}
await utils.start(["backend", "rts"]);
process.exit(errorCode);
}

View File

@ -9,6 +9,7 @@ describe("execCommandReturningOutput", () => {
"hello",
"world",
]);
expect(result).toBe("hello world");
});
@ -18,6 +19,7 @@ describe("execCommandReturningOutput", () => {
"--eval",
"console.log('to out')",
]);
expect(result).toBe("to out");
});
@ -27,6 +29,7 @@ describe("execCommandReturningOutput", () => {
"--eval",
"console.error('to err')",
]);
expect(result).toBe("to err");
});
@ -36,6 +39,7 @@ describe("execCommandReturningOutput", () => {
"--eval",
"console.log('to out'); console.error('to err')",
]);
expect(result).toBe("to out\nto err");
});
@ -45,6 +49,7 @@ describe("execCommandReturningOutput", () => {
"--eval",
"console.error('to err'); console.log('to out')",
]);
expect(result).toBe("to out\nto err");
});
});
@ -56,6 +61,7 @@ describe("execCommandSilent", () => {
test("silences stdout and stderr", async () => {
const consoleSpy = jest.spyOn(console, "log");
await utils.execCommandSilent(["node", "--eval", "console.log('test')"]);
expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore();

View File

@ -1,8 +1,6 @@
// @ts-ignore
import fsPromises from "fs/promises";
import * as Constants from "./constants";
import childProcess from "child_process";
// @ts-ignore
import fs from "node:fs";
import { ConnectionString } from "mongodb-connection-string-url";
@ -42,11 +40,13 @@ export async function start(apps) {
export function getDburl() {
let dbUrl = "";
try {
const env_array = fs
.readFileSync(Constants.ENV_PATH, "utf8")
.toString()
.split("\n");
for (const i in env_array) {
if (
env_array[i].startsWith("APPSMITH_MONGODB_URI") ||
@ -61,14 +61,16 @@ export function getDburl() {
}
const dbEnvUrl =
process.env.APPSMITH_DB_URL || process.env.APPSMITH_MONGO_DB_URI;
// Make sure dbEnvUrl takes precedence over dbUrl
if (dbEnvUrl && dbEnvUrl !== "undefined") {
dbUrl = dbEnvUrl.trim();
}
return dbUrl;
}
export function execCommand(cmd: string[], options?) {
export async function execCommand(cmd: string[], options?) {
return new Promise<void>((resolve, reject) => {
let isPromiseDone = false;
@ -81,7 +83,9 @@ export function execCommand(cmd: string[], options?) {
if (isPromiseDone) {
return;
}
isPromiseDone = true;
if (code === 0) {
resolve();
} else {
@ -93,6 +97,7 @@ export function execCommand(cmd: string[], options?) {
if (isPromiseDone) {
return;
}
isPromiseDone = true;
console.error("Error running command", err);
reject();
@ -100,7 +105,7 @@ export function execCommand(cmd: string[], options?) {
});
}
export function execCommandReturningOutput(cmd, options?) {
export async function execCommandReturningOutput(cmd, options?) {
return new Promise<string>((resolve, reject) => {
const p = childProcess.spawn(cmd[0], cmd.slice(1), options);
@ -125,6 +130,7 @@ export function execCommandReturningOutput(cmd, options?) {
"\n" +
errChunks.join("").trim()
).trim();
if (code === 0) {
resolve(output);
} else {
@ -137,6 +143,7 @@ export function execCommandReturningOutput(cmd, options?) {
export async function listLocalBackupFiles() {
// Ascending order
const backupFiles = [];
await fsPromises
.readdir(Constants.BACKUP_PATH)
.then((filenames) => {
@ -149,6 +156,7 @@ export async function listLocalBackupFiles() {
.catch((err) => {
console.log(err);
});
return backupFiles;
}
@ -160,6 +168,7 @@ export async function updateLastBackupErrorMailSentInMilliSec(ts) {
export async function getLastBackupErrorMailSentInMilliSec() {
try {
const ts = await fsPromises.readFile(Constants.LAST_ERROR_MAIL_TS, "utf8");
return parseInt(ts, 10);
} catch (error) {
return 0;
@ -179,6 +188,7 @@ export function preprocessMongoDBURI(uri /* string */) {
const cs = new ConnectionString(uri);
const params = cs.searchParams;
params.set("appName", "appsmithctl");
if (
@ -205,7 +215,7 @@ export function preprocessMongoDBURI(uri /* string */) {
return cs.toString();
}
export function execCommandSilent(cmd, options?) {
export async function execCommandSilent(cmd, options?) {
return new Promise<void>((resolve, reject) => {
let isPromiseDone = false;
@ -218,7 +228,9 @@ export function execCommandSilent(cmd, options?) {
if (isPromiseDone) {
return;
}
isPromiseDone = true;
if (code === 0) {
resolve();
} else {
@ -230,6 +242,7 @@ export function execCommandSilent(cmd, options?) {
if (isPromiseDone) {
return;
}
isPromiseDone = true;
reject(err);
});
@ -238,5 +251,6 @@ export function execCommandSilent(cmd, options?) {
export function getDatabaseNameFromMongoURI(uri) {
const uriParts = uri.split("/");
return uriParts[uriParts.length - 1].split("?")[0];
}

View File

@ -2,12 +2,14 @@ import * as utils from "./utils";
export async function exec() {
let version = null;
try {
version = await utils.getCurrentAppsmithVersion();
} catch (err) {
console.error("Error fetching current Appsmith version", err);
process.exit(1);
}
if (version) {
console.log(version);
} else {

View File

@ -12820,6 +12820,7 @@ __metadata:
"@shared/ast": "workspace:^"
"@types/express": ^4.17.14
"@types/jest": ^29.2.3
"@types/node": "*"
"@types/nodemailer": ^6.4.17
"@types/readline-sync": ^1.4.8
axios: ^1.7.4