#0 - Implement backup #1
3 changed files with 171 additions and 3 deletions
#0000 - Add docker-volume-manager command and .editorconfig
Introduces `docker-volume-manager` with functionalities to backup and restore Docker volumes. Also adds an .editorconfig file to ensure consistent coding styles across the project.
commit
fd7c4e87e7
11
.editorconfig
Normal file
11
.editorconfig
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
max_line_length = 80
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
|
@ -1,11 +1,166 @@
|
||||||
import { GluegunCommand } from 'gluegun'
|
import {GluegunCommand, GluegunFilesystem} from 'gluegun'
|
||||||
|
import * as process from "process";
|
||||||
|
import {Toolbox} from "gluegun/build/types/domain/toolbox";
|
||||||
|
|
||||||
|
const checkForDocker = async (toolbox: Toolbox) => {
|
||||||
|
const {system, print} = toolbox;
|
||||||
|
|
||||||
|
print.divider()
|
||||||
|
print.info('Checking if the system has docker...');
|
||||||
|
const spinner = print.spin('running docker --version');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await system.run('docker --version');
|
||||||
|
spinner.succeed('Docker is installed on your system!');
|
||||||
|
print.info('Checking if current user can run docker...');
|
||||||
|
spinner.start('running docker ps');
|
||||||
|
try {
|
||||||
|
await system.run('docker ps');
|
||||||
|
spinner.succeed('You can run docker!');
|
||||||
|
} catch (dockerSudoError) {
|
||||||
|
spinner.fail('You are not allowed to run docker, please try again with an allowed user (did you forgot to run the command with sudo?).')
|
||||||
|
process.exit(2)
|
||||||
|
}
|
||||||
|
} catch (dockerVersionError) {
|
||||||
|
spinner.fail('Docker is not installed on your system!')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidPath = (path: string, filesystem: GluegunFilesystem) => filesystem.exists(path) && path !== '' && path !== '/'
|
||||||
|
|
||||||
|
const getAndValidateBackupFolder = async (toolbox: Toolbox): Promise<string> => {
|
||||||
|
const {prompt, print, filesystem} = toolbox;
|
||||||
|
|
||||||
|
print.divider();
|
||||||
|
|
||||||
|
let backupPath: string = ''
|
||||||
|
do {
|
||||||
|
const {pathResponse} = await prompt.ask({
|
||||||
|
type: 'input',
|
||||||
|
name: 'pathResponse',
|
||||||
|
message: 'Please, enter the absolute path to the folder containing the backups'
|
||||||
|
})
|
||||||
|
backupPath = pathResponse
|
||||||
|
|
||||||
|
if (!isValidPath(backupPath, filesystem)) {
|
||||||
|
print.error(`${backupPath} is not a valid path! Please try again.`)
|
||||||
|
print.newline()
|
||||||
|
}
|
||||||
|
} while (!isValidPath(backupPath, filesystem))
|
||||||
|
|
||||||
|
return backupPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectAction = async (toolbox: Toolbox): Promise<string> => {
|
||||||
|
const {prompt, print} = toolbox;
|
||||||
|
|
||||||
|
print.divider();
|
||||||
|
|
||||||
|
const {action} = await prompt.ask({
|
||||||
|
type: 'select',
|
||||||
|
name: 'action',
|
||||||
|
message: 'Select an action:',
|
||||||
|
choices: ['Backup volume', 'Restore volumes', 'exit'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listAndSelectDockerVolumes = async (toolbox: Toolbox): Promise<string[]> => {
|
||||||
|
const {system, prompt, print} = toolbox;
|
||||||
|
|
||||||
|
print.divider();
|
||||||
|
|
||||||
|
const spinner = print.spin('running docker volume ls');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const volumes = await system.run('docker volume ls --format "{{.Name}}"');
|
||||||
|
spinner.succeed('Volumes listed!');
|
||||||
|
const volumeList = volumes.split('\n').filter((volume: string) => volume !== '');
|
||||||
|
const {selectedVolumes} = await prompt.ask({
|
||||||
|
type: 'multiselect',
|
||||||
|
name: 'selectedVolumes',
|
||||||
|
message: 'Select the volumes you want to backup:',
|
||||||
|
choices: volumeList,
|
||||||
|
}) as { selectedVolumes: string[] };
|
||||||
|
|
||||||
|
return selectedVolumes;
|
||||||
|
} catch (dockerVolumeError) {
|
||||||
|
spinner.fail('Failed to list docker volumes!')
|
||||||
|
process.exit(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const runVolumeBackup = async (toolbox: Toolbox, workingDir: string, volumes: string[]) => {
|
||||||
|
const {print, system} = toolbox;
|
||||||
|
|
||||||
|
print.divider();
|
||||||
|
print.info('starting backup process');
|
||||||
|
print.newline();
|
||||||
|
|
||||||
|
const spinner = print.spin();
|
||||||
|
|
||||||
|
for (const volume of volumes) {
|
||||||
|
try {
|
||||||
|
spinner.start(`backing up volume ${volume}...`)
|
||||||
|
|
||||||
|
// improvement: display progress
|
||||||
|
// see: https://superuser.com/questions/168749/is-there-a-way-to-see-any-tar-progress-per-file
|
||||||
|
await system.run(`docker run --rm -v ${volume}:/volume -v ${workingDir}:/tmp alpine sh -c "cd /volume && tar -z -c -p -f /tmp/${volume}.tar ."`);
|
||||||
|
|
||||||
|
spinner.succeed(`volume ${volume} backed up!`)
|
||||||
|
} catch (error) {
|
||||||
|
spinner.fail(`volume ${volume} backup failed!`)
|
||||||
|
print.error(error);
|
||||||
|
print.newline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const command: GluegunCommand = {
|
const command: GluegunCommand = {
|
||||||
name: 'docker-volume-manager',
|
name: 'docker-volume-manager',
|
||||||
run: async (toolbox) => {
|
run: async (toolbox) => {
|
||||||
const { print } = toolbox
|
const {print} = toolbox
|
||||||
|
|
||||||
print.info('Welcome to your CLI')
|
print.info('####################################')
|
||||||
|
print.info('# Welcome to docker-volume-manager #')
|
||||||
|
print.info('####################################')
|
||||||
|
|
||||||
|
// check if docker is present on your system
|
||||||
|
await checkForDocker(toolbox);
|
||||||
|
|
||||||
|
// select action
|
||||||
|
const choice = await selectAction(toolbox);
|
||||||
|
|
||||||
|
if (choice === 'Backup volume') {
|
||||||
|
// select the backup location folder
|
||||||
|
// @ts-ignore
|
||||||
|
const workingFolder = await getAndValidateBackupFolder(toolbox);
|
||||||
|
|
||||||
|
// list and select the volumes
|
||||||
|
const volumes = await listAndSelectDockerVolumes(toolbox);
|
||||||
|
|
||||||
|
// run the backup
|
||||||
|
await runVolumeBackup(toolbox, workingFolder, volumes);
|
||||||
|
|
||||||
|
print.success('Your volume have been backed up!')
|
||||||
|
} else if (choice === 'Restore volumes') {
|
||||||
|
// select the backup location folder
|
||||||
|
// @ts-ignore
|
||||||
|
const workingFolder = await getAndValidateBackupFolder(toolbox);
|
||||||
|
|
||||||
|
// list and select the archives to restore
|
||||||
|
|
||||||
|
// run the restore
|
||||||
|
|
||||||
|
print.success('your backup have been restored!');
|
||||||
|
} else if (choice === 'exit') {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never happen
|
||||||
|
process.exit(999);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { GluegunToolbox } from 'gluegun'
|
import { GluegunToolbox } from 'gluegun'
|
||||||
|
|
||||||
|
|
||||||
|
// Example command, to be rmeoved
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'generate',
|
name: 'generate',
|
||||||
alias: ['g'],
|
alias: ['g'],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue