diff --git a/CHANGELOG.md b/CHANGELOG.md index 401fd02..3a7e1cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Others +* back up and restore files when exist (thanks [@bambeusz](https://github.com/bambeusz)) * remove `macos-10.15` and `ubuntu-18.04` virtual environment ## [2.5.1] - 2023-03-25 diff --git a/lib/index.js b/lib/index.js index 16492d5..c9d107c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2738,6 +2738,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); const fs_1 = __importDefault(__nccwpck_require__(147)); const path_1 = __importDefault(__nccwpck_require__(17)); const core = __importStar(__nccwpck_require__(186)); +const STATE_BACKUP_SUFFIX = "backup-suffix"; const KNOWN_HOSTS = [ "github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=", ]; @@ -2778,6 +2779,7 @@ function setPost() { * setup function */ function setup() { + const backupSuffix = generateBackupSuffix(); // parameters const key = core.getInput("key", { required: true, @@ -2799,6 +2801,7 @@ function setup() { mode: 0o644, flag: "a", }, + mustNotExist: false, }, ]; if (shouldCreateKeyFile(path_1.default.join(sshDirName, name), ifKeyExists)) { @@ -2809,6 +2812,7 @@ function setup() { mode: 0o400, flag: "wx", }, + mustNotExist: true, }); } if (config !== "") { @@ -2819,22 +2823,92 @@ function setup() { mode: 0o644, flag: "a", }, + mustNotExist: false, }); } // create files + const backedUpFileNames = []; for (const file of files) { const fileName = path_1.default.join(sshDirName, file.name); + if (backup(fileName, backupSuffix, file.mustNotExist)) { + backedUpFileNames.push(file.name); + } fs_1.default.writeFileSync(fileName, file.contents, file.options); } console.log(`SSH key has been stored to ${sshDirName} successfully.`); + if (backedUpFileNames.length > 0) { + console.log(`Following files are backed up in suffix "${backupSuffix}"; ${backedUpFileNames.join(", ")}.`); + } } /** * cleanup function */ function cleanup() { - // remove ".ssh" directory - const sshDirName = removeSshDirectory(); - console.log(`SSH key in ${sshDirName} has been removed successfully.`); + const backupSuffix = core.getState(STATE_BACKUP_SUFFIX); + if (backupSuffix === "") { + // remove ".ssh" directory if suffix is not set + const sshDirName = removeSshDirectory(); + console.log(`SSH key in ${sshDirName} has been removed successfully.`); + } + else { + restore(backupSuffix); + } +} +/** + * generate backup suffix name + * @returns backup suffix + */ +function generateBackupSuffix() { + const dirName = getSshDirectory(); + if (!fs_1.default.existsSync(dirName)) { + // do nothing if .ssh does not exist + return ""; + } + const backupSuffix = `.bak-${Date.now()}`; + core.saveState(STATE_BACKUP_SUFFIX, backupSuffix); + return backupSuffix; +} +/** + * back up file + * @param fileName file to back up + * @param backupSuffix suffix + * @param removeOrig remove original file + * @returns is file backed up? + */ +function backup(fileName, backupSuffix, removeOrig) { + if (backupSuffix === "") { + return false; + } + if (!fs_1.default.existsSync(fileName)) { + return false; + } + // move -> copy (in order to keep permissions when restore) + const fileNameBak = `${fileName}${backupSuffix}`; + fs_1.default.renameSync(fileName, fileNameBak); + if (!removeOrig) { + fs_1.default.copyFileSync(fileNameBak, fileName); + } + return true; +} +/** + * restore files + * @param backupSuffix suffix of backup directory + */ +function restore(backupSuffix) { + const dirName = getSshDirectory(); + const keyFileName = core.getInput("name"); + const restoredFileNames = []; + for (const fileName of ["known_hosts", "config", keyFileName]) { + const pathNameOrg = path_1.default.join(dirName, fileName); + const pathNameBak = `${pathNameOrg}${backupSuffix}`; + if (!fs_1.default.existsSync(pathNameBak)) { + continue; + } + fs_1.default.rmSync(pathNameOrg); + fs_1.default.renameSync(pathNameBak, pathNameOrg); + restoredFileNames.push(fileName); + } + console.log(`Following files in suffix "${backupSuffix}" are restored; ${restoredFileNames.join(", ")}`); } /** * create ".ssh" directory @@ -2929,8 +3003,7 @@ function shouldCreateKeyFile(keyFilePath, ifKeyExists) { } switch (ifKeyExists) { case "replace": - // remove file and should create if replace - fs_1.default.unlinkSync(keyFilePath); + // should create if replace (existing file will be backed up when creating) return true; case "ignore": // should NOT create if ignore diff --git a/src/main.ts b/src/main.ts index 6b7e84f..3dc4305 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,8 +7,11 @@ interface FileInfo { name: string; contents: string; options: fs.WriteFileOptions; + mustNotExist: boolean; // file must not exist when creating } +const STATE_BACKUP_SUFFIX = "backup-suffix"; + const KNOWN_HOSTS = [ "github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=", ]; @@ -52,6 +55,8 @@ function setPost(): void { * setup function */ function setup(): void { + const backupSuffix = generateBackupSuffix(); + // parameters const key = core.getInput("key", { required: true, @@ -75,6 +80,7 @@ function setup(): void { mode: 0o644, flag: "a", }, + mustNotExist: false, }, ]; if (shouldCreateKeyFile(path.join(sshDirName, name), ifKeyExists)) { @@ -85,6 +91,7 @@ function setup(): void { mode: 0o400, flag: "wx", }, + mustNotExist: true, }); } if (config !== "") { @@ -95,26 +102,105 @@ function setup(): void { mode: 0o644, flag: "a", }, + mustNotExist: false, }); } // create files + const backedUpFileNames: string[] = []; for (const file of files) { const fileName = path.join(sshDirName, file.name); + if (backup(fileName, backupSuffix, file.mustNotExist)) { + backedUpFileNames.push(file.name); + } + fs.writeFileSync(fileName, file.contents, file.options); } console.log(`SSH key has been stored to ${sshDirName} successfully.`); + if (backedUpFileNames.length > 0) { + console.log(`Following files are backed up in suffix "${backupSuffix}"; ${backedUpFileNames.join(", ")}.`); + } } /** * cleanup function */ function cleanup(): void { - // remove ".ssh" directory - const sshDirName = removeSshDirectory(); + const backupSuffix = core.getState(STATE_BACKUP_SUFFIX); + if (backupSuffix === "") { + // remove ".ssh" directory if suffix is not set + const sshDirName = removeSshDirectory(); - console.log(`SSH key in ${sshDirName} has been removed successfully.`); + console.log(`SSH key in ${sshDirName} has been removed successfully.`); + } else { + restore(backupSuffix); + } +} + +/** + * generate backup suffix name + * @returns backup suffix + */ +function generateBackupSuffix(): string { + const dirName = getSshDirectory(); + if (!fs.existsSync(dirName)) { + // do nothing if .ssh does not exist + return ""; + } + + const backupSuffix = `.bak-${Date.now()}`; + core.saveState(STATE_BACKUP_SUFFIX, backupSuffix); + return backupSuffix; +} + +/** + * back up file + * @param fileName file to back up + * @param backupSuffix suffix + * @param removeOrig remove original file + * @returns is file backed up? + */ +function backup(fileName: string, backupSuffix: string, removeOrig: boolean): boolean { + if (backupSuffix === "") { + return false; + } + if (!fs.existsSync(fileName)) { + return false; + } + + // move -> copy (in order to keep permissions when restore) + const fileNameBak = `${fileName}${backupSuffix}`; + fs.renameSync(fileName, fileNameBak); + if (!removeOrig) { + fs.copyFileSync(fileNameBak, fileName); + } + + return true; +} + +/** + * restore files + * @param backupSuffix suffix of backup directory + */ +function restore(backupSuffix: string): void { + const dirName = getSshDirectory(); + const keyFileName = core.getInput("name"); + + const restoredFileNames: string[] = []; + for (const fileName of ["known_hosts", "config", keyFileName]) { + const pathNameOrg = path.join(dirName, fileName); + const pathNameBak = `${pathNameOrg}${backupSuffix}`; + + if (!fs.existsSync(pathNameBak)) { + continue; + } + + fs.rmSync(pathNameOrg); + fs.renameSync(pathNameBak, pathNameOrg); + restoredFileNames.push(fileName); + } + console.log(`Following files in suffix "${backupSuffix}" are restored; ${restoredFileNames.join(", ")}`); } /** @@ -222,8 +308,7 @@ function shouldCreateKeyFile(keyFilePath: string, ifKeyExists: string): boolean switch (ifKeyExists) { case "replace": - // remove file and should create if replace - fs.unlinkSync(keyFilePath); + // should create if replace (existing file will be backed up when creating) return true; case "ignore":