@topgroup/diginext
Version:
A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.
487 lines (486 loc) • 22.5 kB
JavaScript
;
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;