fix: appsmithctl restore overwrites the MONGODB URI (#22229)

**Fixes:**
  - Overwriting MongoDB env data on appsmithctl restore.
  - appsmithctl backup does not include the MongoDB env vars.

**Features:** 
- appsmithctl backup now does not stop backend and rts service during
backup operation.
  _Note:-
It will be the responsibility of the Admin to make sure that a manual
backup is really a desired snapshot when done via
  the appsmithctl backup util command
To ensure this, Admins may require to stop the backend and rts processes
in the container before a manual backup( We will need to document this)
[ In future we can add an appsmithctl cmd to put appsmith to maintenance
mode with a maintenance page ]_

Fixes # [21603](https://github.com/appsmithorg/appsmith/issues/21603)
This commit is contained in:
Sumesh Pradhan 2023-04-18 06:52:36 +05:30 committed by GitHub
parent a03477ea05
commit 2af62a8dfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 31 additions and 18 deletions

View File

@ -21,8 +21,6 @@ async function run() {
} }
}); });
utils.stop(['backend', 'rts']);
console.log('Available free space at /appsmith-stacks'); console.log('Available free space at /appsmith-stacks');
const availSpaceInBytes = getAvailableBackupSpaceInBytes(); const availSpaceInBytes = getAvailableBackupSpaceInBytes();
console.log('\n'); console.log('\n');
@ -46,7 +44,6 @@ async function run() {
await fsPromises.rm(backupRootPath, { recursive: true, force: true }); await fsPromises.rm(backupRootPath, { recursive: true, force: true });
logger.backup_info('Finished taking a backup at' + archivePath); logger.backup_info('Finished taking a backup at' + archivePath);
await postBackupCleanup();
} catch (err) { } catch (err) {
errorCode = 1; errorCode = 1;
@ -61,7 +58,7 @@ async function run() {
} }
} }
} finally { } finally {
utils.start(['backend', 'rts']); await postBackupCleanup();
process.exit(errorCode); process.exit(errorCode);
} }
} }
@ -91,7 +88,7 @@ async function createManifestFile(path) {
async function exportDockerEnvFile(destFolder) { async function exportDockerEnvFile(destFolder) {
console.log('Exporting docker environment file'); console.log('Exporting docker environment file');
const content = await fsPromises.readFile('/appsmith-stacks/configuration/docker.env', { encoding: 'utf8' }); const content = await fsPromises.readFile('/appsmith-stacks/configuration/docker.env', { encoding: 'utf8' });
const cleaned_content = removeEncryptionEnvData(content) const cleaned_content = removeSensitiveEnvData(content)
await fsPromises.writeFile(destFolder + '/docker.env', cleaned_content); await fsPromises.writeFile(destFolder + '/docker.env', cleaned_content);
console.log('Exporting docker environment file done.'); console.log('Exporting docker environment file done.');
console.log('!!!!!!!!!!!!!!!!!!!!!!!!!! Important !!!!!!!!!!!!!!!!!!!!!!!!!!'); console.log('!!!!!!!!!!!!!!!!!!!!!!!!!! Important !!!!!!!!!!!!!!!!!!!!!!!!!!');
@ -145,11 +142,12 @@ function getBackupContentsPath(backupRootPath, timestamp) {
return backupRootPath + '/appsmith-backup-' + timestamp; return backupRootPath + '/appsmith-backup-' + timestamp;
} }
function removeEncryptionEnvData(content) { function removeSensitiveEnvData(content) {
// Remove encryption and Mongodb data from docker.env
const output_lines = [] const output_lines = []
content.split(/\r?\n/).forEach(line => { content.split(/\r?\n/).forEach(line => {
if (!line.startsWith("APPSMITH_ENCRYPTION")) { if (!line.startsWith("APPSMITH_ENCRYPTION") && !line.startsWith("APPSMITH_MONGODB")) {
output_lines.push(line) output_lines.push(line);
} }
}); });
return output_lines.join('\n') return output_lines.join('\n')
@ -157,7 +155,7 @@ function removeEncryptionEnvData(content) {
function getBackupArchiveLimit(backupArchivesLimit) { function getBackupArchiveLimit(backupArchivesLimit) {
if (!backupArchivesLimit) if (!backupArchivesLimit)
backupArchivesLimit = 4; backupArchivesLimit = Constants.APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT;
return backupArchivesLimit return backupArchivesLimit
} }
@ -179,7 +177,7 @@ function getAvailableBackupSpaceInBytes() {
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 5GB to backup successfully.'); throw new Error('Not enough space avaliable at /appsmith-stacks. Please ensure availability of atleast 2GB to backup successfully.');
} }
} }
@ -195,7 +193,7 @@ module.exports = {
executeMongoDumpCMD, executeMongoDumpCMD,
getGitRoot, getGitRoot,
executeCopyCMD, executeCopyCMD,
removeEncryptionEnvData, removeSensitiveEnvData,
getBackupArchiveLimit, getBackupArchiveLimit,
removeOldBackups removeOldBackups
}; };

View File

@ -24,7 +24,7 @@ it('Checkx the constant is 2 GB', () => {
}); });
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 5GB to backup successfully.'); expect(() => {backup.checkAvailableBackupSpace(size)}).toThrow('Not enough space avaliable at /appsmith-stacks. Please ensure availability of atleast 2GB to backup successfully.');
}); });
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', () => {
@ -88,11 +88,22 @@ it('Checks for the current Appsmith Version.', async () => {
console.log(res) console.log(res)
}) })
test('If encriytpion env values are being removed', () => { test('If Encryption env values are being removed', () => {
expect(backup.removeEncryptionEnvData(`APPSMITH_REDIS_URL=redis://127.0.0.1:6379\nAPPSMITH_ENCRYPTION_PASSWORD=dummy-pass\nAPPSMITH_ENCRYPTION_SALT=dummy-salt\nAPPSMITH_INSTANCE_NAME=Appsmith\n expect(backup.removeSensitiveEnvData(`APPSMITH_REDIS_URL=redis://127.0.0.1:6379\nAPPSMITH_ENCRYPTION_PASSWORD=dummy-pass\nAPPSMITH_ENCRYPTION_SALT=dummy-salt\nAPPSMITH_INSTANCE_NAME=Appsmith\n
`)).toMatch(`APPSMITH_REDIS_URL=redis://127.0.0.1:6379\nAPPSMITH_INSTANCE_NAME=Appsmith\n`) `)).toMatch(`APPSMITH_REDIS_URL=redis://127.0.0.1:6379\nAPPSMITH_INSTANCE_NAME=Appsmith\n`)
}); });
test('If MONGODB env values are being removed', () => {
expect(backup.removeSensitiveEnvData(`APPSMITH_REDIS_URL=redis://127.0.0.1:6379\nAPPSMITH_MONGODB_URI=mongodb://appsmith:pass@localhost:27017/appsmith\nAPPSMITH_MONGODB_USER=appsmith\nAPPSMITH_MONGODB_PASSWORD=pass\nAPPSMITH_INSTANCE_NAME=Appsmith\n
`)).toMatch(`APPSMITH_REDIS_URL=redis://127.0.0.1:6379\nAPPSMITH_INSTANCE_NAME=Appsmith\n`)
});
test('If MONGODB and Encryption env values are being removed', () => {
expect(backup.removeSensitiveEnvData(`APPSMITH_REDIS_URL=redis://127.0.0.1:6379\nAPPSMITH_ENCRYPTION_PASSWORD=dummy-pass\nAPPSMITH_ENCRYPTION_SALT=dummy-salt\nAPPSMITH_MONGODB_URI=mongodb://appsmith:pass@localhost:27017/appsmith\nAPPSMITH_MONGODB_USER=appsmith\nAPPSMITH_MONGODB_PASSWORD=pass\nAPPSMITH_INSTANCE_NAME=Appsmith\n
`)).toMatch(`APPSMITH_REDIS_URL=redis://127.0.0.1:6379\nAPPSMITH_INSTANCE_NAME=Appsmith\n`)
});
test('Backup Archive Limit when env APPSMITH_BACKUP_ARCHIVE_LIMIT is null', () => { test('Backup Archive Limit when env APPSMITH_BACKUP_ARCHIVE_LIMIT is null', () => {
expect(backup.getBackupArchiveLimit()).toBe(4) expect(backup.getBackupArchiveLimit()).toBe(4)
}); });

View File

@ -13,6 +13,8 @@ const MIN_REQUIRED_DISK_SPACE_IN_BYTES = 2147483648 // 2GB
const DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC = 21600000 // 6 hrs const DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC = 21600000 // 6 hrs
const APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT = 4 // 4 backup archives
module.exports = { module.exports = {
BACKUP_PATH, BACKUP_PATH,
RESTORE_PATH, RESTORE_PATH,
@ -21,4 +23,5 @@ module.exports = {
APPSMITHCTL_LOG_PATH, APPSMITHCTL_LOG_PATH,
MIN_REQUIRED_DISK_SPACE_IN_BYTES, MIN_REQUIRED_DISK_SPACE_IN_BYTES,
DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC, DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC,
APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT
} }

View File

@ -58,9 +58,9 @@ async function restoreDockerEnvFile(restoreContentsPath, backupName) {
await utils.execCommand(['cp', restoreContentsPath + '/docker.env', dockerEnvFile]); await utils.execCommand(['cp', restoreContentsPath + '/docker.env', dockerEnvFile]);
if (encryptionPwd && encryptionSalt) { if (encryptionPwd && encryptionSalt) {
const input = readlineSync.question('Existing encryption env values of the previous instance were found.\n\ const input = readlineSync.question('If you are restoring to the same Appsmith deployment which generated the backup archive, you can use the existing encryption keys on the instance.\n\
Press Enter to continue with existing encryption values\n\ Press Enter to continue with existing encryption keys\n\
Or Type "n"/"No" to provide encryption key & password for the new restore instance.\n'); 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(); const answer = input && input.toLocaleUpperCase();
if (answer === 'N' || answer === 'NO') { if (answer === 'N' || answer === 'NO') {
encryptionPwd = readlineSync.question('Enter the APPSMITH_ENCRYPTION_PASSWORD: ', { encryptionPwd = readlineSync.question('Enter the APPSMITH_ENCRYPTION_PASSWORD: ', {
@ -84,7 +84,8 @@ async function restoreDockerEnvFile(restoreContentsPath, backupName) {
} }
await fsPromises.appendFile(dockerEnvFile, '\nAPPSMITH_ENCRYPTION_PASSWORD=' + encryptionPwd + await fsPromises.appendFile(dockerEnvFile, '\nAPPSMITH_ENCRYPTION_PASSWORD=' + encryptionPwd +
'\nAPPSMITH_ENCRYPTION_SALT=' + encryptionSalt); '\nAPPSMITH_ENCRYPTION_SALT=' + encryptionSalt + '\nAPPSMITH_MONGODB_URI=' + process.env.APPSMITH_MONGODB_URI +
'\nAPPSMITH_MONGODB_USER=' + process.env.APPSMITH_MONGODB_USER + '\nAPPSMITH_MONGODB_PASSWORD=' + process.env.APPSMITH_MONGODB_PASSWORD ) ;
console.log('Restoring docker environment file completed'); console.log('Restoring docker environment file completed');
} }