import * as childProcess from 'child_process' ; import * as fs from 'fs-extra' ; import * as path from 'path' ; import * as git from 'simple-git/promise' ; import * as util from 'util' ; import * as uuid from 'uuid' ; import CaptainConstants from './CaptainConstants' ; import Logger from './Logger' ; import Utils from './Utils' ; const exec = util . promisify ( childProcess . exec ) ; export default class GitHelper { private static SSH_PATH_RE = new RegExp ( [ /^\s*/ , /(?:(?<proto>[a-z]+):\/\/)?/ , /(?:(?<user>[a-z_][a-z0-9_-]+)@)?/ , /(?<domain>[^\s\/\?#:]+)/ , /(?::(?<port>[0-9]{1,5}))?/ , /(?:[\/:](?<owner>[^\s\/\?#:]+))?/ , /(?:[\/:](?<repo>[^\s\/\?#:.]+))/ , /(?:.git)?\/?\s*$/ , ] . map ( ( r ) => r . source ) . join ( '' ) , 'i' ) ; static getLastHash ( directory ) { return git ( directory ) . silent ( true ) . raw ( [ 'rev-parse' , 'HEAD' ] ) ; } static clone ( username , pass , sshKey , repo , branch , directory ) { const USER = encodeURIComponent ( username ) ; const PASS = encodeURIComponent ( pass ) ; if ( sshKey ) { const SSH_KEY_PATH = path . join ( CaptainConstants . captainRootDirectoryTemp , uuid . v4 ( ) ) ; const sanitized = GitHelper . sanitizeRepoPathSsh ( repo ) ; const REPO_GIT_PATH = sanitized . repoPath ; const SSH_PORT = sanitized . port ; const DOMAIN = GitHelper . getDomainFromSanitizedSshRepoPath ( REPO_GIT_PATH ) ; Logger . d ( `Cloning SSH ${ REPO_GIT_PATH }` ) ; return Promise . resolve ( ) . then ( function ( ) { return fs . outputFile ( SSH_KEY_PATH , sshKey + '' ) ; } ) . then ( function ( ) { return exec ( `chmod 600 ${ SSH_KEY_PATH }` ) ; } ) . then ( function ( ) { return fs . ensureDir ( '/root/.ssh' ) ; } ) . then ( function ( ) { return exec ( `ssh-keyscan -p ${ SSH_PORT } -H ${ DOMAIN } >> /root/.ssh/known_hosts` ) ; } ) . then ( function ( ) { return git ( ) . silent ( true ) . env ( 'GIT_SSH_COMMAND' , `ssh -i ${ SSH_KEY_PATH }` ) . raw ( [ 'clone' , '--recursive' , '-b' , branch , REPO_GIT_PATH , directory , ] ) ; } ) . then ( function ( ) { return fs . remove ( SSH_KEY_PATH ) ; } ) ; } else { const REPO_PATH = GitHelper . sanitizeRepoPathHttps ( repo ) ; const SCHEME = repo . startsWith ( 'http://' ) ? 'http' : 'https' ; const remote = `${ SCHEME }://${ USER }:${ PASS }@${ REPO_PATH }` ; Logger . dev ( `Cloning HTTPS ${ remote }` ) ; return git ( ) . silent ( true ) . raw ( [ 'clone' , '--recursive' , '-b' , branch , remote , directory ] ) . then ( function ( ) { } ) ; } } static getDomainFromSanitizedSshRepoPath ( input ) { return GitHelper . sanitizeRepoPathSsh ( input ) . domain ; } static sanitizeRepoPathHttps ( input ) { input = Utils . removeHttpHttps ( input ) ; if ( input . toLowerCase ( ) . startsWith ( 'git@' ) ) { input = input . substring ( 4 ) ; input = input . replace ( ':' , '/' ) ; } return input . replace ( /\/$/ , '' ) ; } static sanitizeRepoPathSsh ( input ) { const found = input . match ( GitHelper . SSH_PATH_RE ) ; if ( ! found ) { throw new Error ( `Malformatted SSH path: ${ input }` ) ; } return { user : found . groups ?. user ?? 'git' , domain : found . groups ?. domain , port : Number ( found . groups ?. port ?? 22 ) , owner : found . groups ?. owner ?? '' , repo : found . groups ?. repo , get repoPath ( ) { return `ssh://${ this . user }@${ this . domain }:${ this . port }/${ this . owner }${ this . owner && '/' }${ this . repo }.git` ; } , } ; } }