UNPKG

@topgroup/diginext

Version:

A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.

487 lines (486 loc) 22.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.execGit = exports.checkGitRepoAccess = exports.checkGitProviderAccess = exports.verifySSH = exports.sshKeyContainPassphase = exports.getPublicKey = exports.getSshKeys = exports.sshKeysExisted = exports.generateSSH = exports.writeCustomSSHKeys = exports.addKeysToKnownHosts = exports.createNewPullRequest = exports.getListRepositories = exports.setupRepositoryPermissions = exports.getUserProfile = exports.initializeGitRemote = exports.logout = exports.login = exports.getRepoURLFromRepoSSH = exports.generateRepoURL = exports.generateRepoSSH = void 0; const chalk_1 = __importDefault(require("chalk")); const log_1 = require("diginext-utils/dist/xconsole/log"); const execa_1 = require("execa"); const fs_1 = require("fs"); const capitalize_1 = __importDefault(require("lodash/capitalize")); const open_1 = __importDefault(require("open")); const path_1 = __importDefault(require("path")); const simple_git_1 = require("simple-git"); const yargs_1 = __importDefault(require("yargs")); const const_1 = require("../../config/const"); const SystemTypes_1 = require("../../interfaces/SystemTypes"); const plugins_1 = require("../../plugins"); const slug_1 = require("../../plugins/slug"); // import { conf } from "../.."; const github_1 = __importDefault(require("./github")); // TODO: Implement CRUD of git provider /** * Generate SSH URL of the git repository * @param provider * @example `github` * @param repoSlug - Include username/org slug, exclude ".git" at the end * @example `digitopvn/diginext13` * @returns */ function generateRepoSSH(provider, repoSlug) { return `git@${SystemTypes_1.gitProviderDomain[provider]}:${repoSlug}.git`; } exports.generateRepoSSH = generateRepoSSH; /** * Generate SSH URL of the git repository * @param repoSlug - Include username/org slug, exclude ".git" at the end * @example `digitopvn/diginext13` * @returns */ function generateRepoURL(provider, repoSlug) { return `https://${SystemTypes_1.gitProviderDomain[provider]}/${repoSlug}`; } exports.generateRepoURL = generateRepoURL; /** * Generate SSH URL of the git repository * @param repoSlug - Include username/org slug, exclude ".git" at the end * @example `digitopvn/diginext13` * @returns */ exports.getRepoURLFromRepoSSH = generateRepoURL; const login = async (options) => { const { gitProvider } = options; switch (gitProvider) { case "bitbucket": (0, log_1.logWarn)(`This feature is under development.`); return false; case "github": // logWarn(`This feature is under development.`); await github_1.default.loginWithApp(); break; default: (0, log_1.log)(`Git provider must be specified. Example: ${chalk_1.default.cyan(`dx git login `)}${chalk_1.default.yellow("--github")}`); break; } }; exports.login = login; const logout = async () => { // logout bitbucket account // conf.delete("username"); // conf.delete("token"); // logout github account await github_1.default.logout(); (0, log_1.logSuccess)(`Logged out from all git providers.`); }; exports.logout = logout; /** * @deprecated * Setup "main" branch and "dev/*" branch */ async function initializeGitRemote(options) { const { dir = options.targetDirectory || process.cwd() } = options; // add git origin: const git = (0, simple_git_1.simpleGit)(dir, { binary: "git" }); await git.addRemote("origin", options.repoSSH); await git.push("origin", "main"); // create developer branches const gitUsername = (await git.getConfig(`user.name`, "global")).value; const username = options.username || (gitUsername ? (0, slug_1.makeSlug)(gitUsername).toLowerCase() : undefined) || "developer"; const devBranch = `dev/${username}`; await git.checkout(["-b", devBranch]); await git.push("origin", devBranch); return true; } exports.initializeGitRemote = initializeGitRemote; /** * Get user profile object */ const getUserProfile = async (options) => { const { gitProvider } = options; switch (gitProvider) { case "bitbucket": (0, log_1.logWarn)(`This feature is under development.`); return; case "github": const githubProfile = await github_1.default.profile(); (0, log_1.log)(githubProfile); return githubProfile; default: (0, log_1.log)(`Git provider must be specified. Example: ${chalk_1.default.cyan(`dx git profile `)}${chalk_1.default.yellow("--github")}`); break; } }; exports.getUserProfile = getUserProfile; const setupRepositoryPermissions = async (options) => { (0, log_1.logWarn)(`This feature is under development.`); // await applyBranchPermissions(options, "master", "pull-request-only", "push", []); // await applyBranchPermissions(options, "staging", "pull-request-only", "push", []); // await applyBranchPermissions(options, "staging", "pull-request-only", "require_approvals_to_merge"); // await applyBranchPermissions(options, "prod", "pull-request-only", "push", []); // await applyBranchPermissions(options, "prod", "pull-request-only", "restrict_merges"); // logSuccess(`Đã áp dụng quyền hạn chế truy cập mặc định.`); }; exports.setupRepositoryPermissions = setupRepositoryPermissions; const getListRepositories = async (options) => { (0, log_1.logWarn)(`This feature is under development.`); // await bitbucketAuthentication(options); // await signInBitbucket(options); // return repoList(options); }; exports.getListRepositories = getListRepositories; const createNewPullRequest = async (options) => { // const { gitProvider } = options; const repoInfo = await (0, plugins_1.getCurrentGitRepoData)(options.targetDirectory); if (!repoInfo) return (0, log_1.logError)(`This current directory doesn't have any integrated git remote.`); const { provider: gitProvider } = repoInfo; switch (gitProvider) { case "bitbucket": (0, log_1.logWarn)(`This feature is under development.`); case "github": if (repoInfo.repoURL) { let destBranch = options.thirdAction || "main"; let fromBranch = options.fourAction || repoInfo.branch; (0, open_1.default)(`${repoInfo.repoURL}/compare/${destBranch}...${fromBranch}`); } break; default: (0, log_1.log)(`Git provider must be specified. Example: \n ${chalk_1.default.cyan(`dx git pr`)} ${chalk_1.default.yellow("--github")} <to_branch> \n ${chalk_1.default.cyan(`dx git pr`)} ${chalk_1.default.yellow("--github")} <from_branch> <to_branch>`); break; } }; exports.createNewPullRequest = createNewPullRequest; const addKeysToKnownHosts = async (data) => { const { gitDomain, publicIdRsaFile, privateIdRsaFile } = data; if (!gitDomain) throw new Error(`Git provider's domain is required.`); if (!publicIdRsaFile) throw new Error(`Path to public id_rsa.pub file is required.`); if (!privateIdRsaFile) throw new Error(`Path to prrivate id_rsa file is required.`); // check if "known_hosts" file exists const knownHostsPath = path_1.default.resolve(const_1.SSH_DIR, "known_hosts"); if (!(0, fs_1.existsSync)(knownHostsPath)) await (0, plugins_1.execCmd)(`touch ${knownHostsPath}`); // only scan SSH key of git provider if it's not existed const publicKeyContent = (0, fs_1.readFileSync)(publicIdRsaFile, "utf8"); // const knownHostsContent = readFileSync(knownHostsPath, "utf8"); // await execCmd(`ssh-keyscan ${gitDomain} >> ${knownHostsPath}`); // if (knownHostsContent.indexOf(publicKeyContent) === -1) await execCmd(`ssh-keyscan ${gitDomain} >> ${knownHostsPath}`); try { const writeStream = (0, fs_1.createWriteStream)(knownHostsPath, { flags: "a" }); const subprocess = (0, execa_1.execa)("ssh-keyscan", ["bitbucket.org"]); subprocess.stdout.pipe(writeStream); await subprocess; console.log("Key scan complete. Appended to", knownHostsPath); } catch (error) { console.error("ADD KEY TO KNOWN_HOSTS > Error:", error); } const knownHostsContent = (0, fs_1.readFileSync)(knownHostsPath, "utf8"); console.log("knownHostsContent :>> ", knownHostsContent); // SSH config const sshConfigPath = path_1.default.join(const_1.SSH_DIR, "config"); await (0, plugins_1.execCmd)(`touch ${sshConfigPath}`); const sshConfigContent = (await (0, plugins_1.execCmd)(`cat ${sshConfigPath}`)) || ""; if (sshConfigContent.indexOf(privateIdRsaFile) === -1 && sshConfigContent.indexOf(gitDomain) === -1) { await (0, plugins_1.execCmd)(`echo "Host ${gitDomain}" >> ${sshConfigPath}`); if ((0, plugins_1.isMac)()) await (0, plugins_1.execCmd)(`echo " UseKeychain yes" >> ${sshConfigPath}`); await (0, plugins_1.execCmd)(`echo " AddKeysToAgent yes" >> ${sshConfigPath}`); await (0, plugins_1.execCmd)(`echo " IdentityFile ${privateIdRsaFile}" >> ${sshConfigPath}`); } return { gitDomain, publicIdRsaFile, privateIdRsaFile, sshConfigPath, knownHostsPath }; }; exports.addKeysToKnownHosts = addKeysToKnownHosts; const writeCustomSSHKeys = async (params) => { const { gitDomain, privateKey, publicKey } = params; if (!gitDomain) throw new Error(`[GIT] Write SSH keys > "gitDomain" is required.`); if (!privateKey) throw new Error(`[GIT] Write SSH keys > Content of "privateKey" is required.`); // if (!publicKey) throw new Error(`[GIT] Write SSH keys > Content of "publicKey" is required.`); const idRsaDir = const_1.SSH_DIR; const slug = (0, slug_1.makeSlug)(gitDomain, { delimiter: "_" }); const privateIdRsaFile = path_1.default.resolve(idRsaDir, `id_rsa_${slug}`); const publicIdRsaFile = path_1.default.resolve(idRsaDir, `id_rsa_${slug}.pub`); // delete existing files if ((0, fs_1.existsSync)(privateIdRsaFile)) (0, fs_1.unlinkSync)(privateIdRsaFile); if ((0, fs_1.existsSync)(publicIdRsaFile)) (0, fs_1.unlinkSync)(publicIdRsaFile); // write "privateKey" content to file (0, fs_1.writeFileSync)(privateIdRsaFile, privateKey + "\n", { encoding: "utf8", flag: "a" }); // Make sure the private key is assigned correct permissions (400) try { await (0, execa_1.execaCommand)(`chmod -R 400 ${privateIdRsaFile}`); } catch (e) { throw new Error(`[GIT] Can't assign permission [400] to "id_rsa" private key.`); } // write "publicKey" to file event if "publicKey" is not provided if (!publicKey) { const subprocess = (0, execa_1.execa)("ssh-keygen", ["-y", "-f", privateIdRsaFile]); subprocess.stdout.pipe((0, fs_1.createWriteStream)(publicIdRsaFile)); await subprocess; } else { (0, fs_1.writeFileSync)(publicIdRsaFile, publicKey, "utf8"); } // [TEST] remove old keys // await execCmd(`ssh-keygen -R ${gitDomain}`); // add keys to "know_hosts" await (0, exports.addKeysToKnownHosts)({ gitDomain, privateIdRsaFile, publicIdRsaFile }); // Start SSH agent & add private keys: eval `ssh-agent` // await execCmd(`eval $(ssh-agent -s) && ssh-add ${privateIdRsaFile}`); const sshAddResult = await (0, execa_1.execaCommand)(`ssh-add ${privateIdRsaFile}`, { // This is important to execute the command in a shell shell: true, }); console.log("[CUSTOM SSH] SSH Add Result :>> ", sshAddResult); // print results (0, log_1.log)(`[GIT] Added new SSH keys on this machine:`); (0, log_1.log)(` - Public key:`, publicIdRsaFile); (0, log_1.log)(` - Private key:`, privateIdRsaFile); // console.log("privateIdRsaFile :>> ", readFileSync(privateIdRsaFile, "utf8")); // console.log("publicIdRsaFile :>> ", readFileSync(publicIdRsaFile, "utf8")); return { gitDomain, privateIdRsaFile, publicIdRsaFile }; }; exports.writeCustomSSHKeys = writeCustomSSHKeys; const generateSSH = async (options) => { // const { gitProvider } = options; // Check if any "id_rsa" existed const idRsaDir = const_1.SSH_DIR; // const idRsaDir = path.resolve(CLI_DIR, "storage/home/ssh"); // log(`idRsaDir:`, idRsaDir, `>> Existed: ${existsSync(idRsaDir)}`); let publicIdRsaFile, privateIdRsaFile; if ((0, fs_1.existsSync)(idRsaDir)) { const globby = require("globby"); const files = await globby(idRsaDir + "/id_*"); // log(`existed "id_rsa" files >>`, files); if (files.length > 0) { publicIdRsaFile = files.find((f) => f.indexOf(".pub") > -1); privateIdRsaFile = files.find((f) => f.indexOf(".pub") == -1); // Make sure the private key is assigned correct permissions (400) await (0, plugins_1.execCmd)(`chmod -R 400 ${privateIdRsaFile}`, `Can't assign permission [400] to "id_rsa" private key.`); } } else { await (0, plugins_1.execCmd)(`mkdir -p ${idRsaDir}`, `Can't create '${idRsaDir}' directory`); } // If no "id_rsa" existed -> generate one: ssh-keygen -b 2048 -t rsa -p -f $HOME/.ssh/id_rsa -q -N "" -> id_rsa id_rsa.pub if (!publicIdRsaFile) { privateIdRsaFile = path_1.default.resolve(idRsaDir, "id_rsa"); publicIdRsaFile = path_1.default.resolve(idRsaDir, "id_rsa.pub"); try { await (0, execa_1.execa)("ssh-keygen", ["-b", "2048", "-t", "rsa", "-f", privateIdRsaFile, "-q", "-N", ""]); } catch (e) { (0, log_1.logError)(`Can't generate SSH private & public key:`, e); throw new Error(`Can't generate SSH private & public key: ${e}`); } await (0, plugins_1.wait)(500); } (0, log_1.log)(`SSH keys on this machine:`); (0, log_1.log)(`- Public key:`, publicIdRsaFile); (0, log_1.log)(`- Private key:`, privateIdRsaFile); // set permission 400 to id_rsa files await (0, plugins_1.execCmd)(`chmod -R 400 ${privateIdRsaFile}`, `Can't set permission 400 to ${privateIdRsaFile}.`); // Start SSH agent & add private keys: eval `ssh-agent` // await execCmd(`eval $(ssh-agent -s) && ssh-add ${privateIdRsaFile}`); const sshAddResult = await (0, execa_1.execaCommand)(`ssh-add ${privateIdRsaFile}`, { // This is important to execute the command in a shell shell: true, }); console.log("[GENERATE SSH] SSH Add Result :>> ", sshAddResult); // Return PUBLIC key to add to GIT provider const publicKeyContent = (0, fs_1.readFileSync)(publicIdRsaFile, "utf8"); (0, log_1.logSuccess)(`Copy this public key content & paste to GIT provider:`); (0, log_1.log)(publicKeyContent); return publicKeyContent; }; exports.generateSSH = generateSSH; const sshKeysExisted = async (options) => { let publicIdRsaFile = options === null || options === void 0 ? void 0 : options.publicIdRsaFile; let privateIdRsaFile = options === null || options === void 0 ? void 0 : options.privateIdRsaFile; if (!(0, fs_1.existsSync)(const_1.SSH_DIR)) { (0, log_1.logWarn)(`[GIT] SSH directory is not existed.`); return false; } if (!publicIdRsaFile || !privateIdRsaFile) { const globby = require("globby"); const files = await globby(const_1.SSH_DIR + "/id_*"); if (files.length > 0) { publicIdRsaFile = files.find((f) => f.indexOf(".pub") > -1); privateIdRsaFile = files.find((f) => f.indexOf(".pub") == -1); // Make sure the private key is assigned correct permissions (400) try { await (0, execa_1.execaCommand)(`chmod -R 400 ${privateIdRsaFile}`); } catch (e) { (0, log_1.logError)(`[GIT] Can't assign permission [400] to "id_rsa" private key.`); return false; } } else { (0, log_1.logWarn)(`[GIT] PUBLIC_KEY and PRIVATE_KEY are not existed.`); return false; } } else { (0, log_1.logWarn)(`[GIT] PUBLIC_KEY and PRIVATE_KEY are not existed.`); return false; } return true; }; exports.sshKeysExisted = sshKeysExisted; const getSshKeys = async (options) => { const privateIdRsaFile = (options === null || options === void 0 ? void 0 : options.privateIdRsaFile) || path_1.default.resolve(const_1.SSH_DIR, "id_rsa"); const publicIdRsaFile = (options === null || options === void 0 ? void 0 : options.publicIdRsaFile) || path_1.default.resolve(const_1.SSH_DIR, "id_rsa.pub"); if (!(0, fs_1.existsSync)(privateIdRsaFile)) throw new Error(`Private SSH key is not existed.`); if (!(0, fs_1.existsSync)(publicIdRsaFile)) throw new Error(`Public SSH key is not existed.`); const privateKey = (0, fs_1.readFileSync)(privateIdRsaFile, "utf8"); const publicKey = (0, fs_1.readFileSync)(publicIdRsaFile, "utf8"); return { privateKey, publicKey }; }; exports.getSshKeys = getSshKeys; const getPublicKey = async (filePath) => { const publicIdRsaFile = filePath || path_1.default.resolve(const_1.SSH_DIR, "id_rsa.pub"); if (!(0, fs_1.existsSync)(publicIdRsaFile)) throw new Error(`PUBLIC_KEY is not existed.`); const publicKey = (0, fs_1.readFileSync)(publicIdRsaFile, "utf8"); return { publicKey }; }; exports.getPublicKey = getPublicKey; const sshKeyContainPassphase = (options) => { const idRsaFile = (options === null || options === void 0 ? void 0 : options.sshFile) || path_1.default.resolve(const_1.HOME_DIR, ".ssh/id_rsa"); try { (0, execa_1.execaSync)("ssh-keygen", ["-y", "-f", idRsaFile]); return false; } catch (e) { return true; } }; exports.sshKeyContainPassphase = sshKeyContainPassphase; const verifySSH = async (options) => { const { gitProvider } = options; let authResult; switch (gitProvider) { case "bitbucket": authResult = await (0, plugins_1.execCmd)(`ssh -o StrictHostKeyChecking=no -T git@bitbucket.org`, "[GIT] Bitbucket authentication failed"); authResult = typeof authResult !== "undefined"; break; case "github": // has to use this because "Github does not provide shell access" try { await (0, execa_1.execaCommand)(`ssh -o StrictHostKeyChecking=no -T git@github.com`); authResult = true; } catch (e) { authResult = e.toString().indexOf("successfully authenticated") > -1; } break; // case "gitlab": // authResult = await execCmd(`ssh -o StrictHostKeyChecking=no -T git@gitlab.com`, "[GIT] Gitlab authentication failed"); // authResult = typeof authResult !== "undefined"; // break; default: authResult = false; break; } if (authResult) { (0, log_1.logSuccess)(`[GIT] ✅ ${(0, capitalize_1.default)(gitProvider)} was authenticated successfully.`); } else { (0, log_1.logError)(`[GIT] ❌ Provider "${gitProvider}" is not valid.`); } return authResult; }; exports.verifySSH = verifySSH; /** * Check if the client machine can access to the git provider publicly. */ const checkGitProviderAccess = async (gitProvider) => { let result; switch (gitProvider) { case "bitbucket": result = await (0, plugins_1.execCmd)(`ssh -o StrictHostKeyChecking=no -T git@bitbucket.org`, "Bitbucket authentication failed"); break; case "github": // has to use this because "Github does not provide shell access" try { result = await (0, execa_1.execaCommand)(`ssh -o StrictHostKeyChecking=no -T git@github.com`); } catch (e) { result = e.toString().indexOf("successfully authenticated") > -1 ? true : undefined; } break; // case "gitlab": // result = await execCmd(`ssh -o StrictHostKeyChecking=no -T git@gitlab.com`, "Gitlab authentication failed"); // break; default: (0, log_1.logError)(`Git provider "${gitProvider}" is not valid.`); result = false; break; } return typeof result !== "undefined" ? true : false; }; exports.checkGitProviderAccess = checkGitProviderAccess; /** * Check if the client machine can access to the PRIVATE git repository. */ const checkGitRepoAccess = async (repoSSH) => { let result = await (0, plugins_1.execCmd)(`git ls-remote ${repoSSH}`, `You don't have access to this repo: ${repoSSH}`); return result ? true : false; }; exports.checkGitRepoAccess = checkGitRepoAccess; async function execGit(options) { // if (typeof options.targetDirectory == "undefined") options.targetDirectory = process.cwd(); const { secondAction, thirdAction } = options; switch (secondAction) { case "ssh": switch (thirdAction) { case "generate": case "register": case "add": await (0, exports.generateSSH)(options); return; case "verify": await (0, exports.verifySSH)(options); return; default: yargs_1.default.command("git", "").showHelp(); break; } case "apply-permissions": case "allow": await (0, exports.setupRepositoryPermissions)(options); break; case "login": await (0, exports.login)(options); break; case "logout": await (0, exports.logout)(); break; case "profile": await (0, exports.getUserProfile)(options); break; case "pr": case "pullrequest": await (0, exports.createNewPullRequest)(options); break; case "repo": case "repos": await (0, exports.getListRepositories)(options); break; default: yargs_1.default.command("git", "").showHelp(); break; } return options; } exports.execGit = execGit;