From 3945e42c532843222d09e213861f1cf848995b93 Mon Sep 17 00:00:00 2001 From: chyoo Date: Wed, 15 Jul 2026 16:01:09 +0900 Subject: [PATCH] feat: add quick deploy for launcher backend builds --- src/apps/apps.service.ts | 84 +++++++++++++++++++++++++++++----------- src/apps/dto.ts | 7 +++- 2 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/apps/apps.service.ts b/src/apps/apps.service.ts index 03efad8..681fbc9 100644 --- a/src/apps/apps.service.ts +++ b/src/apps/apps.service.ts @@ -954,31 +954,70 @@ export class AppsService { async createDeploymentJob(body: CreateDeploymentJobDto, requester?: AdminRequester) { const namespace = normalizeNamespace(body.namespace); const environment = body.environment.trim(); - const release = await this.prisma.appRelease.findUnique({ - where: { id: body.releaseId }, - include: { - app: { - include: { - components: true, - }, - }, - }, - }); + if (body.releaseId && body.buildJobId) { + throw new BadRequestException('릴리즈 또는 빌드 중 하나만 배포 대상으로 선택할 수 있습니다.'); + } - if (!release) { + const release = body.releaseId + ? await this.prisma.appRelease.findUnique({ + where: { id: body.releaseId }, + include: { + app: { + include: { + components: true, + }, + }, + }, + }) + : null; + const quickBuild = body.buildJobId + ? await this.prisma.buildJob.findUnique({ + where: { id: body.buildJobId }, + include: { + component: { + include: { + app: { + include: { components: true }, + }, + }, + }, + }, + }) + : null; + + if (body.releaseId && !release) { throw new NotFoundException('릴리즈를 찾을 수 없습니다.'); } - - if (release.status !== 'SUCCEEDED') { + if (body.buildJobId && !quickBuild) { + throw new NotFoundException('빌드 작업을 찾을 수 없습니다.'); + } + if (!release && !quickBuild) { + throw new BadRequestException('배포할 릴리즈 또는 빌드를 선택하세요.'); + } + if (release && release.status !== 'SUCCEEDED') { throw new BadRequestException('성공한 릴리즈만 배포할 수 있습니다.'); } + if (quickBuild && quickBuild.status !== 'SUCCEEDED') { + throw new BadRequestException('성공한 빌드만 빠른 배포할 수 있습니다.'); + } + if (quickBuild && !['CONTAINER', 'STATIC_WEB'].includes(quickBuild.component.type)) { + throw new BadRequestException('컨테이너 이미지 빌드만 빠른 배포할 수 있습니다.'); + } + if (quickBuild && !quickBuild.image) { + throw new BadRequestException('배포할 컨테이너 이미지가 없습니다.'); + } - const imageTags = parseReleaseImageTags(release.imageTags); - const images = imageTags.images; + const app = release?.app ?? quickBuild!.component.app; + const images = release + ? parseReleaseImageTags(release.imageTags).images + : { [quickBuild!.component.name]: quickBuild!.image! }; + const deploymentLabel = release + ? `${app.name} ${release.version}` + : `${app.name} ${quickBuild!.component.name} ${quickBuild!.commitSha?.slice(0, 7) ?? 'quick'}`; const requestedResources = body.resources; const runtimeProfile = await this.prisma.runtimeEnvProfile.findFirst({ where: { - appId: release.appId, + appId: app.id, environment, namespace, }, @@ -1002,8 +1041,8 @@ export class AppsService { const deploymentJob = await this.prisma.deploymentJob.create({ data: { - appId: release.appId, - releaseId: release.id, + appId: app.id, + releaseId: release?.id, environment, namespace, status: 'RUNNING', @@ -1011,7 +1050,8 @@ export class AppsService { ...this.requesterData(requester), startedAt: new Date(), logs: [ - `[running] deployment started: ${release.app.name} ${release.version}`, + `[running] deployment started: ${deploymentLabel}`, + ...(quickBuild ? [`[source] quick build ${quickBuild.id} ${quickBuild.image}`] : []), `[target] environment=${environment} namespace=${namespace}`, ].join('\n'), }, @@ -1026,7 +1066,7 @@ export class AppsService { for (const [componentName, image] of Object.entries(images)) { const deploymentName = sanitizeK8sName(componentName); - const component = release.app.components.find((item) => item.name === componentName); + const component = app.components.find((item) => item.name === componentName); const workloadType = normalizeWorkloadType(component?.workloadType, component?.type); const deploymentExists = workloadType === 'StatefulSet' ? await this.statefulSetExists(namespace, deploymentName) @@ -1173,7 +1213,7 @@ export class AppsService { } } - if (body.updateLauncherAppVersion !== false) { + if (release && body.updateLauncherAppVersion !== false) { try { await this.launcherService.publishAppReleaseToLauncher(release.id); logs.push('[launcher] app version updated'); @@ -1182,7 +1222,7 @@ export class AppsService { logs.push(`[launcher] app version update failed: ${launcherMessage}`); } } else { - logs.push('[launcher] app version update skipped'); + logs.push(quickBuild ? '[launcher] app version update skipped for quick deployment' : '[launcher] app version update skipped'); } logs.push('[done] deployment completed'); diff --git a/src/apps/dto.ts b/src/apps/dto.ts index 20ab684..eceb470 100644 --- a/src/apps/dto.ts +++ b/src/apps/dto.ts @@ -220,8 +220,13 @@ export class DeploymentResourceLimitsDto { } export class CreateDeploymentJobDto { + @IsOptional() @IsUUID() - releaseId!: string; + releaseId?: string; + + @IsOptional() + @IsUUID() + buildJobId?: string; @IsString() environment!: string;