UNPKG

@dxdeveloperexperience/hygie

Version:

Hygie is an easy-to-use Open-Source REST API allowing you to interact with GIT events. This NestJS API expose a set of customizable rules to automate your project's life cycle.

308 lines (268 loc) 9.21 kB
import { logger } from '../logger/logger.service'; import { HttpService } from '@nestjs/common'; import { GitTypeEnum } from '../webhook/utils.enum'; import { Utils } from '../utils/utils'; import { catchError } from 'rxjs/operators'; import { throwError, from as fromObs } from 'rxjs'; import { GithubService } from '../github/github.service'; import { GitlabService } from '../gitlab/gitlab.service'; import { GitApiInfos } from '../git/gitApiInfos'; import { GitIssueInfos } from '../git/gitIssueInfos'; import { FileSizeException } from '../exceptions/fileSize.exception'; import { DataAccessService } from '../data_access/dataAccess.service'; import { Constants } from '../utils/constants'; import { GitFileInfos } from '../git/gitFileInfos'; const fs = require('fs-extra'); const path = require('path'); interface ConfigEnv { gitRepo: string; gitApi: string; gitToken: string; } export class RemoteConfigUtils { static MAX_SIZE: number = 1000000; static getGitRawPath( whichGit: GitTypeEnum, projectURL: string, filePath: string, branch: string = 'master', ) { let result: string = ''; switch (whichGit) { case GitTypeEnum.Github: result = `https://raw.githubusercontent.com/${Utils.getRepositoryFullName( projectURL, )}/${branch}/${filePath}`; break; case GitTypeEnum.Gitlab: result = `${projectURL.replace('.git', '')}/raw/${branch}/${filePath}`; break; } return result; } static async checkDownloadSize( httpService: HttpService, url: string, ): Promise<boolean> { const size: number = await httpService .head(url) .toPromise() .then(response => { return response.headers['content-length']; }) .catch(err => { throw err; }); if (size > this.MAX_SIZE) { return false; } return true; } static getGitType(url: string): GitTypeEnum { return url.indexOf('github.com') > -1 ? GitTypeEnum.Github : url.indexOf('gitlab.com') > -1 ? GitTypeEnum.Gitlab : GitTypeEnum.Undefined; } /** * Download the `.rulesrc` from the repository associate to the `projectURL`. * @param projectURL * @return the location of the `.hygie` repo */ static async downloadRulesFile( dataAccessService: DataAccessService, httpService: HttpService, githubService: GithubService, gitlabService: GitlabService, projectURL: string, filename: string, branch: string = 'master', defaultBranch?: string, ): Promise<string> { const disableRemoteConfig: boolean = process.env.DISABLE_REMOTE_CONFIG === 'true'; const whichGit: GitTypeEnum = this.getGitType(projectURL); const gitService: GithubService | GitlabService = whichGit === GitTypeEnum.Github ? githubService : gitlabService; const repositoryFullName = Utils.getRepositoryFullName(projectURL); await gitService .setEnvironmentVariables(dataAccessService, repositoryFullName) .catch(err => { throw err; }); // Used to check size const rulesFilePath: string = this.getGitRawPath( whichGit, projectURL, `.hygie/${filename}`, branch, ); const hygieFolder: string = 'remote-rules/' + repositoryFullName + '/.hygie'; // If we don't allow fetching remote .rulesrc file if (disableRemoteConfig && filename === Constants.rulesExtension) { logger.warn('Not allowed to fetch remote file!'); // Get default configuration const data = fs.readFileSync( path.join(__dirname, `../rules/${Constants.rulesExtension}`), ); await dataAccessService.writeRule(`${hygieFolder}/${filename}`, data); return hygieFolder; } // Check size try { const checkSize: boolean = await RemoteConfigUtils.checkDownloadSize( httpService, rulesFilePath, ).catch(err => { if (err.response.status !== 404) { throw err; } else { return true; } }); if (!checkSize) { throw new FileSizeException(rulesFilePath); } } catch (e) { throw e; } const gitFileInfos = new GitFileInfos(); gitFileInfos.filePath = `.hygie/${filename}`; gitFileInfos.fileBranch = branch; const observable = fromObs(gitService.getFileContent(gitFileInfos)); // Download file await observable .pipe( catchError(() => { if (filename === Constants.rulesExtension) { gitFileInfos.fileBranch = defaultBranch; const getDefaultBranchConfigFile = defaultBranch !== branch ? fromObs(gitService.getFileContent(gitFileInfos)) : throwError(''); return getDefaultBranchConfigFile.pipe( catchError(() => { logger.warn( `No ${ Constants.rulesExtension } file founded. Using the default one.`, { project: projectURL, location: 'downloadRulesFile' }, ); // return default REMOTE file return httpService.get(process.env.DEFAULT_CONFIGURATION_FILE); }), ); } else { return throwError(`${rulesFilePath} do not exist!`); } }), ) .toPromise() .then(async response => { const resultData = response.data; await dataAccessService.writeRule( `${hygieFolder}/${filename}`, resultData, ); return hygieFolder; }) .catch(err => { throw err; }); return hygieFolder; } static async getGitlabProjectId( httpService: HttpService, gitApi: string, repositoryFullName: string, ): Promise<string> { return httpService .get(`${gitApi}/projects/${encodeURIComponent(repositoryFullName)}`) .toPromise() .then(response => response.data.id); } /** * Create the `config.env` file with `gitApi` URL and the corresponding `gitToken` * @return an Object with the success status (true if registration succeed, false otherwise) and if the file already exist */ static async registerConfigEnv( dataAccessService: DataAccessService, httpService: HttpService, githubService: GithubService, gitlabService: GitlabService, configEnv: ConfigEnv, applicationURL: string, ): Promise<any> { const result: any = { succeed: true, alreadyExist: false, }; const repositoryFullName = Utils.getRepositoryFullName(configEnv.gitRepo); const configFile: string = `remote-envs/${repositoryFullName}/config.env`; const content: any = { gitApi: configEnv.gitApi, gitToken: configEnv.gitToken, }; if (await dataAccessService.checkIfEnvExist(configFile)) { result.alreadyExist = true; } const gitApiInfos: GitApiInfos = new GitApiInfos(); gitApiInfos.git = Utils.whichGitType(configEnv.gitRepo); gitApiInfos.repositoryFullName = repositoryFullName; content.git = gitApiInfos.git; await dataAccessService.writeEnv(configFile, content); // Add also the hygie-env config.env const username = repositoryFullName.split('/')[0]; const hygieEnvVarConfigFile = `remote-envs/${username}/hygie-env/config.env`; await dataAccessService.writeEnv(hygieEnvVarConfigFile, content); /** * Create a `Connected to Hygie!` issue * and * Create a Webhook */ const gitIssueInfos = new GitIssueInfos(); gitIssueInfos.title = 'Connected to Hygie!'; let issueNumber: number; if (gitApiInfos.git === GitTypeEnum.Github) { await githubService.setEnvironmentVariables( dataAccessService, repositoryFullName, ); issueNumber = await githubService.createIssue(gitIssueInfos); result.issue = `${configEnv.gitRepo}/issues/${issueNumber}`; githubService.createWebhook(applicationURL + '/webhook'); } else if (gitApiInfos.git === GitTypeEnum.Gitlab) { gitApiInfos.projectId = await this.getGitlabProjectId( httpService, configEnv.gitApi, gitApiInfos.repositoryFullName, ); // Store the projectId content.gitlabId = gitApiInfos.projectId; await dataAccessService.writeEnv(configFile, content); // Add also the hygie-env config.env await dataAccessService.writeEnv(hygieEnvVarConfigFile, content); await gitlabService.setEnvironmentVariables( dataAccessService, repositoryFullName, ); issueNumber = await gitlabService.createIssue(gitIssueInfos); result.issue = `${configEnv.gitRepo}/issues/${issueNumber}`; gitlabService.createWebhook(applicationURL + '/webhook'); } return result; } static getAccessToken(result: string) { const from = result.indexOf('access_token='); const size = 'access_token='.length; result = result.substring(from + size); let to = result.indexOf('&'); if (to === -1) { to = result.length; } const accessToken = result.substring(0, to); return accessToken; } }