paginate app operations and refine runtime settings layout
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -9,6 +9,8 @@ AUTH_SERVER_URL=http://localhost:4100
|
|||||||
GITEA_BASE_URL=http://localhost:3000
|
GITEA_BASE_URL=http://localhost:3000
|
||||||
GITEA_APP_ORG=platform
|
GITEA_APP_ORG=platform
|
||||||
GITEA_ADMIN_TOKEN=
|
GITEA_ADMIN_TOKEN=
|
||||||
|
GITEA_RELEASE_PUSH_WHITELIST=
|
||||||
|
GITEA_RELEASE_MERGE_WHITELIST=
|
||||||
BUILD_REGISTRY=localhost:5000
|
BUILD_REGISTRY=localhost:5000
|
||||||
BUILD_ARTIFACT_INSTALL_COMMAND=npm ci
|
BUILD_ARTIFACT_INSTALL_COMMAND=npm ci
|
||||||
LAUNCHER_BUILD_COMMAND="npm ci && npm run build:linux"
|
LAUNCHER_BUILD_COMMAND="npm ci && npm run build:linux"
|
||||||
|
|||||||
+40
-18
@@ -1143,26 +1143,52 @@ export class AppsService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void this.finalizeMigrationJob(migrationJob.id, {
|
||||||
|
namespace,
|
||||||
|
jobName,
|
||||||
|
image,
|
||||||
|
command,
|
||||||
|
envSecretName,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { migrationJob };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async finalizeMigrationJob(
|
||||||
|
migrationJobId: string,
|
||||||
|
input: {
|
||||||
|
namespace: string;
|
||||||
|
jobName: string;
|
||||||
|
image: string;
|
||||||
|
command: string;
|
||||||
|
envSecretName: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const migrationJob = await this.prisma.runtimeControlJob.findUnique({
|
||||||
|
where: { id: migrationJobId },
|
||||||
|
});
|
||||||
|
if (!migrationJob) return;
|
||||||
|
|
||||||
const logs = splitDeploymentLogs(migrationJob.logs);
|
const logs = splitDeploymentLogs(migrationJob.logs);
|
||||||
try {
|
try {
|
||||||
logs.push(`[apply] job/${jobName}`);
|
logs.push(`[apply] job/${input.jobName}`);
|
||||||
appendOutput(
|
appendOutput(
|
||||||
logs,
|
logs,
|
||||||
await this.applyK8sManifest(createMigrationJobManifest({
|
await this.applyK8sManifest(createMigrationJobManifest({
|
||||||
namespace,
|
namespace: input.namespace,
|
||||||
jobName,
|
jobName: input.jobName,
|
||||||
image,
|
image: input.image,
|
||||||
command,
|
command: input.command,
|
||||||
envSecretName,
|
envSecretName: input.envSecretName,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
logs.push(`[wait] job/${jobName}`);
|
logs.push(`[wait] job/${input.jobName}`);
|
||||||
await this.waitForJobResult(namespace, jobName, logs);
|
await this.waitForJobResult(input.namespace, input.jobName, logs);
|
||||||
logs.push(`[logs] job/${jobName}`);
|
logs.push(`[logs] job/${input.jobName}`);
|
||||||
appendOutput(logs, await this.kubectl(['logs', `job/${jobName}`, '-n', namespace]));
|
appendOutput(logs, await this.kubectl(['logs', `job/${input.jobName}`, '-n', input.namespace]));
|
||||||
logs.push('[done] migration completed');
|
logs.push('[done] migration completed');
|
||||||
|
|
||||||
const completed = await this.prisma.runtimeControlJob.update({
|
await this.prisma.runtimeControlJob.update({
|
||||||
where: { id: migrationJob.id },
|
where: { id: migrationJob.id },
|
||||||
data: {
|
data: {
|
||||||
status: 'SUCCEEDED',
|
status: 'SUCCEEDED',
|
||||||
@@ -1170,19 +1196,17 @@ export class AppsService {
|
|||||||
logs: logs.join('\n'),
|
logs: logs.join('\n'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return { migrationJob: completed };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Migration failed';
|
const message = error instanceof Error ? error.message : 'Migration failed';
|
||||||
try {
|
try {
|
||||||
logs.push(`[logs] job/${jobName}`);
|
logs.push(`[logs] job/${input.jobName}`);
|
||||||
appendOutput(logs, await this.kubectl(['logs', `job/${jobName}`, '-n', namespace]));
|
appendOutput(logs, await this.kubectl(['logs', `job/${input.jobName}`, '-n', input.namespace]));
|
||||||
} catch {
|
} catch {
|
||||||
// 실패한 Job 로그가 아직 없을 수 있습니다.
|
// 실패한 Job 로그가 아직 없을 수 있습니다.
|
||||||
}
|
}
|
||||||
logs.push(`[failed] ${message}`);
|
logs.push(`[failed] ${message}`);
|
||||||
|
|
||||||
const failed = await this.prisma.runtimeControlJob.update({
|
await this.prisma.runtimeControlJob.update({
|
||||||
where: { id: migrationJob.id },
|
where: { id: migrationJob.id },
|
||||||
data: {
|
data: {
|
||||||
status: 'FAILED',
|
status: 'FAILED',
|
||||||
@@ -1190,8 +1214,6 @@ export class AppsService {
|
|||||||
logs: logs.join('\n'),
|
logs: logs.join('\n'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return { migrationJob: failed };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export class GiteaService {
|
|||||||
private readonly baseUrl: string;
|
private readonly baseUrl: string;
|
||||||
private readonly appOrg: string;
|
private readonly appOrg: string;
|
||||||
private readonly token?: string;
|
private readonly token?: string;
|
||||||
|
private readonly releaseBranchPushUsers: string[];
|
||||||
|
private readonly releaseBranchMergeUsers: string[];
|
||||||
|
|
||||||
constructor(configService: ConfigService) {
|
constructor(configService: ConfigService) {
|
||||||
this.baseUrl = configService
|
this.baseUrl = configService
|
||||||
@@ -41,6 +43,12 @@ export class GiteaService {
|
|||||||
.replace(/\/$/, '');
|
.replace(/\/$/, '');
|
||||||
this.appOrg = configService.get<string>('GITEA_APP_ORG', 'platform');
|
this.appOrg = configService.get<string>('GITEA_APP_ORG', 'platform');
|
||||||
this.token = configService.get<string>('GITEA_ADMIN_TOKEN') || undefined;
|
this.token = configService.get<string>('GITEA_ADMIN_TOKEN') || undefined;
|
||||||
|
this.releaseBranchPushUsers = parseWhiteList(
|
||||||
|
configService.get<string>('GITEA_RELEASE_PUSH_WHITELIST'),
|
||||||
|
);
|
||||||
|
this.releaseBranchMergeUsers = parseWhiteList(
|
||||||
|
configService.get<string>('GITEA_RELEASE_MERGE_WHITELIST'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getVersion() {
|
async getVersion() {
|
||||||
@@ -159,6 +167,10 @@ export class GiteaService {
|
|||||||
await this.createDefaultBranches(repo.owner.login, repo.name, repo.default_branch, body);
|
await this.createDefaultBranches(repo.owner.login, repo.name, repo.default_branch, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (body.createReleaseBranch !== false) {
|
||||||
|
await this.protectReleaseBranch(repo.owner.login, repo.name);
|
||||||
|
}
|
||||||
|
|
||||||
return { repo: mapRepo(repo), branches: await this.listBranches(repo.owner.login, repo.name) };
|
return { repo: mapRepo(repo), branches: await this.listBranches(repo.owner.login, repo.name) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +186,10 @@ export class GiteaService {
|
|||||||
await this.createDefaultBranches(org, repo.name, repo.default_branch, body);
|
await this.createDefaultBranches(org, repo.name, repo.default_branch, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (body.createReleaseBranch !== false) {
|
||||||
|
await this.protectReleaseBranch(org, repo.name);
|
||||||
|
}
|
||||||
|
|
||||||
return { repo: mapRepo(repo), branches: await this.listBranches(org, repo.name) };
|
return { repo: mapRepo(repo), branches: await this.listBranches(org, repo.name) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +257,42 @@ export class GiteaService {
|
|||||||
return createdBranches;
|
return createdBranches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async protectReleaseBranch(owner: string, repo: string) {
|
||||||
|
const me = await this.getAuthenticatedUser();
|
||||||
|
const pushUsernames = uniqueUsers([
|
||||||
|
...this.releaseBranchPushUsers,
|
||||||
|
...(me?.login ? [me.login] : []),
|
||||||
|
]);
|
||||||
|
const mergeUsernames = uniqueUsers([
|
||||||
|
...this.releaseBranchMergeUsers,
|
||||||
|
...(me?.login ? [me.login] : []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
branch_name: 'release',
|
||||||
|
enable_push: true,
|
||||||
|
enable_push_whitelist: true,
|
||||||
|
push_whitelist_usernames: pushUsernames,
|
||||||
|
enable_merge_whitelist: true,
|
||||||
|
merge_whitelist_usernames: mergeUsernames,
|
||||||
|
enable_force_push: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.request(`/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/branch_protections`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
allowedStatuses: [409],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAuthenticatedUser() {
|
||||||
|
try {
|
||||||
|
return await this.request<{ login: string }>('/api/v1/user');
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async request<T>(
|
private async request<T>(
|
||||||
path: string,
|
path: string,
|
||||||
init?: RequestInit & { allowedStatuses?: number[] },
|
init?: RequestInit & { allowedStatuses?: number[] },
|
||||||
@@ -373,6 +425,30 @@ function mapRepo(repo: GiteaRepo) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseWhiteList(raw?: string) {
|
||||||
|
if (!raw) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw
|
||||||
|
.split(',')
|
||||||
|
.map((value) => value.trim())
|
||||||
|
.filter((value) => value.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function uniqueUsers(values: string[]) {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const next: string[] = [];
|
||||||
|
|
||||||
|
for (const value of values) {
|
||||||
|
if (seen.has(value)) continue;
|
||||||
|
seen.add(value);
|
||||||
|
next.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
function toCreateRepoPayload(body: CreateGiteaRepoDto) {
|
function toCreateRepoPayload(body: CreateGiteaRepoDto) {
|
||||||
return {
|
return {
|
||||||
name: body.name,
|
name: body.name,
|
||||||
|
|||||||
Reference in New Issue
Block a user