chore: Remove shelljs use from backup command (#37356)
This is towards fixing `appsmithctl` into a modern component of the project. First order of business is to get rid of the `shelljs` dependency, since we don't really need it that seriously, and since it does weird stuff with `require()` that we can't use `esbuild` to build `appsmithctl`. Further PRs will remove `shelljs` from other commands as well, and then we'll remove `shelljs` from our dependencies. ## Automation /test sanity ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/11814325698> > Commit: 98261ee8c8a52090b1e9bb27a27be4be0874b83f > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11814325698&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Sanity` > Spec: > <hr>Wed, 13 Nov 2024 09:58:36 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 - **New Features** - Improved backup process with enhanced error handling and asynchronous checks for available disk space. - New function for executing commands with output capture added to utility functions. - **Bug Fixes** - Refined error messages for insufficient disk space and password mismatches during backup. - **Tests** - Expanded test suite for backup utilities and command execution, ensuring robust coverage of various scenarios. - **Documentation** - Updated formatting of numeric constants for better readability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
d90faa2e61
commit
748a5eccb4
|
|
@ -1,7 +1,6 @@
|
||||||
const fsPromises = require('fs/promises');
|
const fsPromises = require('fs/promises');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const shell = require('shelljs');
|
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const Constants = require('./constants');
|
const Constants = require('./constants');
|
||||||
const logger = require('./logger');
|
const logger = require('./logger');
|
||||||
|
|
@ -16,17 +15,18 @@ async function run() {
|
||||||
let errorCode = 0;
|
let errorCode = 0;
|
||||||
let backupRootPath, archivePath, encryptionPassword;
|
let backupRootPath, archivePath, encryptionPassword;
|
||||||
let encryptArchive = false;
|
let encryptArchive = false;
|
||||||
try {
|
|
||||||
const check_supervisord_status_cmd = '/usr/bin/supervisorctl >/dev/null 2>&1';
|
|
||||||
shell.exec(check_supervisord_status_cmd, function (code) {
|
|
||||||
if (code > 0) {
|
|
||||||
shell.echo('application is not running, starting supervisord');
|
|
||||||
shell.exec('/usr/bin/supervisord');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
await utils.execCommandSilent(["/usr/bin/supervisorctl"]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Supervisor is not running, exiting.');
|
||||||
|
process.exitCode = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
console.log('Available free space at /appsmith-stacks');
|
console.log('Available free space at /appsmith-stacks');
|
||||||
const availSpaceInBytes = getAvailableBackupSpaceInBytes();
|
const availSpaceInBytes = getAvailableBackupSpaceInBytes("/appsmith-stacks");
|
||||||
console.log('\n');
|
console.log('\n');
|
||||||
|
|
||||||
checkAvailableBackupSpace(availSpaceInBytes);
|
checkAvailableBackupSpace(availSpaceInBytes);
|
||||||
|
|
@ -232,13 +232,14 @@ function getTimeStampInISO() {
|
||||||
return new Date().toISOString().replace(/:/g, '-')
|
return new Date().toISOString().replace(/:/g, '-')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableBackupSpaceInBytes() {
|
async function getAvailableBackupSpaceInBytes(path) {
|
||||||
return parseInt(shell.exec('df --output=avail -B 1 /appsmith-stacks | tail -n 1'), 10)
|
const stat = await fsPromises.statfs(path);
|
||||||
|
return stat.bsize * stat.bfree;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkAvailableBackupSpace(availSpaceInBytes) {
|
function checkAvailableBackupSpace(availSpaceInBytes) {
|
||||||
if (availSpaceInBytes < Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES) {
|
if (availSpaceInBytes < Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES) {
|
||||||
throw new Error('Not enough space avaliable at /appsmith-stacks. Please ensure availability of atleast 2GB to backup successfully.');
|
throw new Error("Not enough space available at /appsmith-stacks. Please ensure availability of at least 2GB to backup successfully.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,4 +260,4 @@ module.exports = {
|
||||||
removeOldBackups,
|
removeOldBackups,
|
||||||
getEncryptionPasswordFromUser,
|
getEncryptionPasswordFromUser,
|
||||||
encryptBackupArchive,
|
encryptBackupArchive,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ const Constants = require('./constants');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const fsPromises = require('fs/promises');
|
const fsPromises = require('fs/promises');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const shell = require('shelljs');
|
|
||||||
const readlineSync = require('readline-sync');
|
const readlineSync = require('readline-sync');
|
||||||
|
|
||||||
describe('Backup Tests', () => {
|
describe('Backup Tests', () => {
|
||||||
|
|
@ -13,19 +12,19 @@ test('Timestamp string in ISO format', () => {
|
||||||
expect(backup.getTimeStampInISO()).toMatch(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\-(\d{2})\-(\d{2})\.(\d{3})Z/)
|
expect(backup.getTimeStampInISO()).toMatch(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\-(\d{2})\-(\d{2})\.(\d{3})Z/)
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Available Space in /appsmith-stacks volume in Bytes', () => {
|
test('Available Space in /appsmith-stacks volume in Bytes', async () => {
|
||||||
shell.exec = jest.fn((format) => '20');
|
const res = expect(await backup.getAvailableBackupSpaceInBytes("/"))
|
||||||
const res = expect(backup.getAvailableBackupSpaceInBytes())
|
res.toBeGreaterThan(1024 * 1024)
|
||||||
res.toBe(20)
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Checkx the constant is 2 GB', () => {
|
it('Checkx the constant is 2 GB', () => {
|
||||||
let size = 2 * 1024 * 1024 * 1024
|
let size = 2 * 1024 * 1024 * 1024
|
||||||
expect(Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES).toBe(size)
|
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', () => {
|
it('Should throw Error when the available size is below MIN_REQUIRED_DISK_SPACE_IN_BYTES', () => {
|
||||||
let size = Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES - 1;
|
let size = Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES - 1;
|
||||||
expect(() => {backup.checkAvailableBackupSpace(size)}).toThrow('Not enough space avaliable at /appsmith-stacks. Please ensure availability of atleast 2GB to backup successfully.');
|
expect(() => backup.checkAvailableBackupSpace(size)).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should not hould throw Error when the available size is >= MIN_REQUIRED_DISK_SPACE_IN_BYTES', () => {
|
it('Should not hould throw Error when the available size is >= MIN_REQUIRED_DISK_SPACE_IN_BYTES', () => {
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ const LAST_ERROR_MAIL_TS = "/appsmith-stacks/data/backup/last-error-mail-ts"
|
||||||
|
|
||||||
const ENV_PATH = "/appsmith-stacks/configuration/docker.env"
|
const ENV_PATH = "/appsmith-stacks/configuration/docker.env"
|
||||||
|
|
||||||
const MIN_REQUIRED_DISK_SPACE_IN_BYTES = 2147483648 // 2GB
|
const MIN_REQUIRED_DISK_SPACE_IN_BYTES = 2_147_483_648 // 2GB
|
||||||
|
|
||||||
const DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC = 21600000 // 6 hrs
|
const DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC = 21_600_000 // 6 hrs
|
||||||
|
|
||||||
const APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT = 4 // 4 backup archives
|
const APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT = 4 // 4 backup archives
|
||||||
|
|
||||||
|
|
@ -27,4 +27,4 @@ module.exports = {
|
||||||
DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC,
|
DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC,
|
||||||
APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT,
|
APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT,
|
||||||
ENV_PATH
|
ENV_PATH
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,9 +154,10 @@ function execCommandSilent(cmd, options) {
|
||||||
|
|
||||||
const p = childProcess.spawn(cmd[0], cmd.slice(1), {
|
const p = childProcess.spawn(cmd[0], cmd.slice(1), {
|
||||||
...options,
|
...options,
|
||||||
|
stdio: "ignore",
|
||||||
});
|
});
|
||||||
|
|
||||||
p.on("exit", (code) => {
|
p.on("close", (code) => {
|
||||||
if (isPromiseDone) {
|
if (isPromiseDone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -173,8 +174,7 @@ function execCommandSilent(cmd, options) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isPromiseDone = true;
|
isPromiseDone = true;
|
||||||
console.error("Error running command", err);
|
reject(err);
|
||||||
reject();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
deploy/docker/fs/opt/appsmith/utils/bin/utils.test.js
Normal file
22
deploy/docker/fs/opt/appsmith/utils/bin/utils.test.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
const { describe, test, expect } = require("@jest/globals");
|
||||||
|
const utils = require("./utils");
|
||||||
|
|
||||||
|
describe("execCommandSilent", () => {
|
||||||
|
|
||||||
|
test("Runs a command", async () => {
|
||||||
|
await utils.execCommandSilent(["echo"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles errors silently", async () => {
|
||||||
|
await expect(utils.execCommandSilent(["nonexistentcommand"]))
|
||||||
|
.rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user