mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
437 lines
13 KiB
TypeScript
437 lines
13 KiB
TypeScript
import {
|
|
GetGitHubToken,
|
|
GetGitHubUsername,
|
|
GetLocalRepositoryPath,
|
|
GetOneUptimeURL,
|
|
GetRepositorySecretKey,
|
|
} from "../Config";
|
|
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
|
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
|
import URL from "Common/Types/API/URL";
|
|
import CodeRepositoryType from "Common/Types/CodeRepository/CodeRepositoryType";
|
|
import PullRequest from "Common/Types/CodeRepository/PullRequest";
|
|
import PullRequestState from "Common/Types/CodeRepository/PullRequestState";
|
|
import BadDataException from "Common/Types/Exception/BadDataException";
|
|
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
|
import API from "Common/Utils/API";
|
|
import CodeRepositoryServerUtil from "CommonServer/Utils/CodeRepository/CodeRepository";
|
|
import GitHubUtil from "CommonServer/Utils/CodeRepository/GitHub/GitHub";
|
|
import logger from "CommonServer/Utils/Logger";
|
|
import CodeRepositoryModel from "Model/Models/CodeRepository";
|
|
import ServiceRepository from "Model/Models/ServiceRepository";
|
|
|
|
export interface CodeRepositoryResult {
|
|
codeRepository: CodeRepositoryModel;
|
|
servicesRepository: Array<ServiceRepository>;
|
|
}
|
|
|
|
export default class CodeRepositoryUtil {
|
|
public static codeRepositoryResult: CodeRepositoryResult | null = null;
|
|
public static gitHubUtil: GitHubUtil | null = null;
|
|
|
|
public static getGitHubUtil(): GitHubUtil {
|
|
if (!this.gitHubUtil) {
|
|
const gitHubToken: string | null = GetGitHubToken();
|
|
|
|
const gitHubUsername: string | null = GetGitHubUsername();
|
|
|
|
if (!gitHubUsername) {
|
|
throw new BadDataException("GitHub Username is required");
|
|
}
|
|
|
|
if (!gitHubToken) {
|
|
throw new BadDataException("GitHub Token is required");
|
|
}
|
|
|
|
this.gitHubUtil = new GitHubUtil({
|
|
authToken: gitHubToken,
|
|
username: gitHubUsername!,
|
|
});
|
|
}
|
|
|
|
return this.gitHubUtil;
|
|
}
|
|
|
|
public static async pullChanges(): Promise<void> {
|
|
await CodeRepositoryServerUtil.pullChanges({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
});
|
|
}
|
|
|
|
public static getBranchName(data: {
|
|
branchName: string;
|
|
serviceRepository: ServiceRepository;
|
|
}): string {
|
|
if (!data.serviceRepository.serviceCatalog) {
|
|
throw new BadDataException("Service Catalog is required");
|
|
}
|
|
|
|
if (!data.serviceRepository.serviceCatalog.name) {
|
|
throw new BadDataException("Service Catalog Name is required");
|
|
}
|
|
|
|
return (
|
|
"oneuptime-" +
|
|
data.serviceRepository.serviceCatalog?.name?.toLowerCase() +
|
|
"-" +
|
|
data.branchName
|
|
);
|
|
}
|
|
|
|
public static async createBranch(data: {
|
|
branchName: string;
|
|
serviceRepository: ServiceRepository;
|
|
}): Promise<void> {
|
|
const branchName: string = this.getBranchName({
|
|
branchName: data.branchName,
|
|
serviceRepository: data.serviceRepository,
|
|
});
|
|
|
|
await CodeRepositoryServerUtil.createBranch({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
branchName: branchName,
|
|
});
|
|
}
|
|
|
|
public static async createOrCheckoutBranch(data: {
|
|
serviceRepository: ServiceRepository;
|
|
branchName: string;
|
|
}): Promise<void> {
|
|
const branchName: string = this.getBranchName({
|
|
branchName: data.branchName,
|
|
serviceRepository: data.serviceRepository,
|
|
});
|
|
|
|
await CodeRepositoryServerUtil.createOrCheckoutBranch({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
branchName: branchName,
|
|
});
|
|
}
|
|
|
|
public static async writeToFile(data: {
|
|
filePath: string;
|
|
content: string;
|
|
}): Promise<void> {
|
|
await CodeRepositoryServerUtil.writeToFile({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
filePath: data.filePath,
|
|
content: data.content,
|
|
});
|
|
}
|
|
|
|
public static async createDirectory(data: {
|
|
directoryPath: string;
|
|
}): Promise<void> {
|
|
await CodeRepositoryServerUtil.createDirectory({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
directoryPath: data.directoryPath,
|
|
});
|
|
}
|
|
|
|
public static async deleteFile(data: { filePath: string }): Promise<void> {
|
|
await CodeRepositoryServerUtil.deleteFile({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
filePath: data.filePath,
|
|
});
|
|
}
|
|
|
|
public static async deleteDirectory(data: {
|
|
directoryPath: string;
|
|
}): Promise<void> {
|
|
await CodeRepositoryServerUtil.deleteDirectory({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
directoryPath: data.directoryPath,
|
|
});
|
|
}
|
|
|
|
public static async discardChanges(): Promise<void> {
|
|
await CodeRepositoryServerUtil.discardChanges({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
});
|
|
}
|
|
|
|
public static async checkoutBranch(data: {
|
|
branchName: string;
|
|
}): Promise<void> {
|
|
await CodeRepositoryServerUtil.checkoutBranch({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
branchName: data.branchName,
|
|
});
|
|
}
|
|
|
|
public static async checkoutMainBranch(): Promise<void> {
|
|
const codeRepository: CodeRepositoryModel = await this.getCodeRepository();
|
|
|
|
if (!codeRepository.mainBranchName) {
|
|
throw new BadDataException("Main Branch Name is required");
|
|
}
|
|
|
|
await this.checkoutBranch({
|
|
branchName: codeRepository.mainBranchName!,
|
|
});
|
|
}
|
|
|
|
public static async addFilesToGit(data: {
|
|
filePaths: Array<string>;
|
|
}): Promise<void> {
|
|
await CodeRepositoryServerUtil.addFilesToGit({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
filePaths: data.filePaths,
|
|
});
|
|
}
|
|
|
|
public static async commitChanges(data: { message: string }): Promise<void> {
|
|
let username: string | null = null;
|
|
|
|
if (
|
|
this.codeRepositoryResult?.codeRepository.repositoryHostedAt ===
|
|
CodeRepositoryType.GitHub
|
|
) {
|
|
username = GetGitHubUsername();
|
|
}
|
|
|
|
if (!username) {
|
|
throw new BadDataException("Username is required");
|
|
}
|
|
|
|
await CodeRepositoryServerUtil.commitChanges({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
message: data.message,
|
|
username: username,
|
|
});
|
|
}
|
|
|
|
public static async pushChanges(data: {
|
|
branchName: string;
|
|
serviceRepository: ServiceRepository;
|
|
}): Promise<void> {
|
|
const branchName: string = this.getBranchName({
|
|
branchName: data.branchName,
|
|
serviceRepository: data.serviceRepository,
|
|
});
|
|
|
|
const codeRepository: CodeRepositoryModel = await this.getCodeRepository();
|
|
|
|
if (!codeRepository.mainBranchName) {
|
|
throw new BadDataException("Main Branch Name is required");
|
|
}
|
|
|
|
if (!codeRepository.organizationName) {
|
|
throw new BadDataException("Organization Name is required");
|
|
}
|
|
|
|
if (!codeRepository.repositoryName) {
|
|
throw new BadDataException("Repository Name is required");
|
|
}
|
|
|
|
if (codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub) {
|
|
return await this.getGitHubUtil().pushChanges({
|
|
repoPath: GetLocalRepositoryPath(),
|
|
branchName: branchName,
|
|
organizationName: codeRepository.organizationName,
|
|
repositoryName: codeRepository.repositoryName,
|
|
});
|
|
}
|
|
}
|
|
|
|
public static async switchToMainBranch(): Promise<void> {
|
|
const codeRepository: CodeRepositoryModel = await this.getCodeRepository();
|
|
|
|
if (!codeRepository.mainBranchName) {
|
|
throw new BadDataException("Main Branch Name is required");
|
|
}
|
|
|
|
await this.checkoutBranch({
|
|
branchName: codeRepository.mainBranchName!,
|
|
});
|
|
}
|
|
|
|
public static async createPullRequest(data: {
|
|
branchName: string;
|
|
title: string;
|
|
body: string;
|
|
serviceRepository: ServiceRepository;
|
|
}): Promise<PullRequest> {
|
|
const branchName: string = this.getBranchName({
|
|
branchName: data.branchName,
|
|
serviceRepository: data.serviceRepository,
|
|
});
|
|
|
|
const codeRepository: CodeRepositoryModel = await this.getCodeRepository();
|
|
|
|
if (!codeRepository.mainBranchName) {
|
|
throw new BadDataException("Main Branch Name is required");
|
|
}
|
|
|
|
if (!codeRepository.organizationName) {
|
|
throw new BadDataException("Organization Name is required");
|
|
}
|
|
|
|
if (!codeRepository.repositoryName) {
|
|
throw new BadDataException("Repository Name is required");
|
|
}
|
|
|
|
if (codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub) {
|
|
return await this.getGitHubUtil().createPullRequest({
|
|
headBranchName: branchName,
|
|
baseBranchName: codeRepository.mainBranchName,
|
|
organizationName: codeRepository.organizationName,
|
|
repositoryName: codeRepository.repositoryName,
|
|
title: data.title,
|
|
body: data.body,
|
|
});
|
|
}
|
|
throw new BadDataException("Code Repository type not supported");
|
|
}
|
|
|
|
public static async getServicesToImproveCode(data: {
|
|
codeRepository: CodeRepositoryModel;
|
|
services: Array<ServiceRepository>;
|
|
}): Promise<Array<ServiceRepository>> {
|
|
const servicesToImproveCode: Array<ServiceRepository> = [];
|
|
|
|
for (const service of data.services) {
|
|
if (!data.codeRepository.mainBranchName) {
|
|
throw new BadDataException("Main Branch Name is required");
|
|
}
|
|
|
|
if (!data.codeRepository.organizationName) {
|
|
throw new BadDataException("Organization Name is required");
|
|
}
|
|
|
|
if (!data.codeRepository.repositoryName) {
|
|
throw new BadDataException("Repository Name is required");
|
|
}
|
|
|
|
if (!service.limitNumberOfOpenPullRequestsCount) {
|
|
throw new BadDataException(
|
|
"Limit Number Of Open Pull Requests Count is required",
|
|
);
|
|
}
|
|
|
|
if (
|
|
data.codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub
|
|
) {
|
|
const gitHuhbToken: string | null = GetGitHubToken();
|
|
|
|
if (!gitHuhbToken) {
|
|
throw new BadDataException("GitHub Token is required");
|
|
}
|
|
|
|
const numberOfPullRequestForThisService: number =
|
|
await this.getGitHubUtil().getNumberOfPullRequestsExistForService({
|
|
serviceRepository: service,
|
|
pullRequestState: PullRequestState.Open,
|
|
baseBranchName: data.codeRepository.mainBranchName,
|
|
organizationName: data.codeRepository.organizationName,
|
|
repositoryName: data.codeRepository.repositoryName,
|
|
});
|
|
|
|
if (
|
|
numberOfPullRequestForThisService <
|
|
service.limitNumberOfOpenPullRequestsCount
|
|
) {
|
|
servicesToImproveCode.push(service);
|
|
logger.info(
|
|
`Service ${service.serviceCatalog?.name} has ${numberOfPullRequestForThisService} open pull requests. Limit is ${service.limitNumberOfOpenPullRequestsCount}. Adding to the list to improve code...`,
|
|
);
|
|
} else {
|
|
logger.warn(
|
|
`Service ${service.serviceCatalog?.name} has ${numberOfPullRequestForThisService} open pull requests. Limit is ${service.limitNumberOfOpenPullRequestsCount}. Skipping...`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return servicesToImproveCode;
|
|
}
|
|
|
|
public static async getCodeRepositoryResult(): Promise<CodeRepositoryResult> {
|
|
if (this.codeRepositoryResult) {
|
|
return this.codeRepositoryResult;
|
|
}
|
|
|
|
const repositorySecretKey: string | null = GetRepositorySecretKey();
|
|
|
|
if (!repositorySecretKey) {
|
|
throw new BadDataException("Repository Secret Key is required");
|
|
}
|
|
|
|
const url: URL = URL.fromString(
|
|
GetOneUptimeURL().toString() + "/api",
|
|
).addRoute(
|
|
`${new CodeRepositoryModel()
|
|
.getCrudApiPath()
|
|
?.toString()}/get-code-repository/${repositorySecretKey}`,
|
|
);
|
|
|
|
const codeRepositoryResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
|
await API.get(url);
|
|
|
|
if (codeRepositoryResult instanceof HTTPErrorResponse) {
|
|
throw codeRepositoryResult;
|
|
}
|
|
|
|
const codeRepository: CodeRepositoryModel = CodeRepositoryModel.fromJSON(
|
|
codeRepositoryResult.data["codeRepository"] as JSONObject,
|
|
CodeRepositoryModel,
|
|
) as CodeRepositoryModel;
|
|
|
|
const servicesRepository: Array<ServiceRepository> = (
|
|
codeRepositoryResult.data["servicesRepository"] as JSONArray
|
|
).map((serviceRepository: JSONObject) => {
|
|
return ServiceRepository.fromJSON(
|
|
serviceRepository,
|
|
ServiceRepository,
|
|
) as ServiceRepository;
|
|
});
|
|
|
|
if (!codeRepository) {
|
|
throw new BadDataException(
|
|
"Code Repository not found with the secret key provided.",
|
|
);
|
|
}
|
|
|
|
if (!servicesRepository || servicesRepository.length === 0) {
|
|
throw new BadDataException(
|
|
"No services attached to this repository. Please attach services to this repository on OneUptime Dashboard.",
|
|
);
|
|
}
|
|
|
|
logger.info(`Code Repository found: ${codeRepository.name}`);
|
|
|
|
logger.info("Services found in the repository:");
|
|
|
|
servicesRepository.forEach((serviceRepository: ServiceRepository) => {
|
|
logger.info(`- ${serviceRepository.serviceCatalog?.name}`);
|
|
});
|
|
|
|
this.codeRepositoryResult = {
|
|
codeRepository,
|
|
servicesRepository,
|
|
};
|
|
|
|
return this.codeRepositoryResult;
|
|
}
|
|
|
|
public static async getCodeRepository(): Promise<CodeRepositoryModel> {
|
|
if (!this.codeRepositoryResult) {
|
|
const result: CodeRepositoryResult = await this.getCodeRepositoryResult();
|
|
return result.codeRepository;
|
|
}
|
|
|
|
return this.codeRepositoryResult.codeRepository;
|
|
}
|
|
|
|
public static async getServiceRepositories(): Promise<
|
|
Array<ServiceRepository>
|
|
> {
|
|
if (!this.codeRepositoryResult) {
|
|
const result: CodeRepositoryResult = await this.getCodeRepositoryResult();
|
|
return result.servicesRepository;
|
|
}
|
|
|
|
return this.codeRepositoryResult.servicesRepository;
|
|
}
|
|
}
|