support launcher backend source and image builds
This commit is contained in:
@@ -33,6 +33,10 @@ export class CreateLauncherVersionDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateLauncherSourceDto {
|
export class UpdateLauncherSourceDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsIn(['launcher-client', 'launcher-backend'])
|
||||||
|
target?: 'launcher-client' | 'launcher-backend';
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
repository!: string;
|
repository!: string;
|
||||||
|
|
||||||
@@ -43,6 +47,14 @@ export class UpdateLauncherSourceDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
commitSha?: string;
|
commitSha?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
image?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
dockerfile?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LinkLauncherVersionSourceDto {
|
export class LinkLauncherVersionSourceDto {
|
||||||
|
|||||||
@@ -23,9 +23,14 @@ import {
|
|||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
const LAUNCHER_SOURCE_SETTING_KEYS = {
|
const LAUNCHER_SOURCE_SETTING_KEYS = {
|
||||||
repository: 'launcher.source.repository',
|
repository: 'launcher.client.source.repository',
|
||||||
branch: 'launcher.source.branch',
|
branch: 'launcher.client.source.branch',
|
||||||
commitSha: 'launcher.source.commit_sha',
|
commitSha: 'launcher.client.source.commit_sha',
|
||||||
|
backendRepository: 'launcher.backend.source.repository',
|
||||||
|
backendBranch: 'launcher.backend.source.branch',
|
||||||
|
backendCommitSha: 'launcher.backend.source.commit_sha',
|
||||||
|
backendImage: 'launcher.backend.image',
|
||||||
|
backendDockerfile: 'launcher.backend.dockerfile',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -101,11 +106,51 @@ export class LauncherService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateSource(body: UpdateLauncherSourceDto) {
|
async updateSource(body: UpdateLauncherSourceDto) {
|
||||||
|
const target = body.target ?? 'launcher-client';
|
||||||
const repository = normalizeRepository(body.repository);
|
const repository = normalizeRepository(body.repository);
|
||||||
const branch = body.branch?.trim() || 'release';
|
const branch = body.branch?.trim() || 'release';
|
||||||
const commitSha = body.commitSha?.trim() || '';
|
const commitSha = body.commitSha?.trim() || '';
|
||||||
|
const isBackend = target === 'launcher-backend';
|
||||||
|
const image = body.image?.trim() || this.configService.get<string>('LAUNCHER_BACKEND_IMAGE', 'localhost:5000/studio-launcher-be');
|
||||||
|
const dockerfile = body.dockerfile?.trim() || 'Dockerfile';
|
||||||
|
|
||||||
await this.launcherPrisma.$transaction([
|
const writes = isBackend ? [
|
||||||
|
this.upsertCommonSettingByKey(
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendRepository,
|
||||||
|
'런처 백엔드 소스 저장소',
|
||||||
|
'source',
|
||||||
|
repository,
|
||||||
|
'런처 백엔드 컨테이너 빌드에 사용할 저장소',
|
||||||
|
),
|
||||||
|
this.upsertCommonSettingByKey(
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendBranch,
|
||||||
|
'런처 백엔드 소스 브랜치',
|
||||||
|
'source',
|
||||||
|
branch,
|
||||||
|
'런처 백엔드 컨테이너 빌드 기준 브랜치',
|
||||||
|
),
|
||||||
|
this.upsertCommonSettingByKey(
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendCommitSha,
|
||||||
|
'런처 백엔드 소스 커밋',
|
||||||
|
'source',
|
||||||
|
commitSha,
|
||||||
|
'비워두면 브랜치 최신 커밋 기준',
|
||||||
|
),
|
||||||
|
this.upsertCommonSettingByKey(
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendImage,
|
||||||
|
'런처 백엔드 이미지',
|
||||||
|
'source',
|
||||||
|
image,
|
||||||
|
'런처 백엔드 컨테이너 이미지 이름',
|
||||||
|
),
|
||||||
|
this.upsertCommonSettingByKey(
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendDockerfile,
|
||||||
|
'런처 백엔드 Dockerfile',
|
||||||
|
'source',
|
||||||
|
dockerfile,
|
||||||
|
'런처 백엔드 빌드에 사용할 Dockerfile 경로',
|
||||||
|
),
|
||||||
|
] : [
|
||||||
this.upsertCommonSettingByKey(
|
this.upsertCommonSettingByKey(
|
||||||
LAUNCHER_SOURCE_SETTING_KEYS.repository,
|
LAUNCHER_SOURCE_SETTING_KEYS.repository,
|
||||||
'런처 소스 저장소',
|
'런처 소스 저장소',
|
||||||
@@ -127,9 +172,19 @@ export class LauncherService {
|
|||||||
commitSha,
|
commitSha,
|
||||||
'비워두면 브랜치 최신 커밋 기준',
|
'비워두면 브랜치 최신 커밋 기준',
|
||||||
),
|
),
|
||||||
]);
|
];
|
||||||
|
|
||||||
return { source: { repository, branch, commitSha: commitSha || null } };
|
await this.launcherPrisma.$transaction(writes);
|
||||||
|
|
||||||
|
return {
|
||||||
|
source: {
|
||||||
|
repository,
|
||||||
|
branch,
|
||||||
|
commitSha: commitSha || null,
|
||||||
|
image: isBackend ? image : undefined,
|
||||||
|
dockerfile: isBackend ? dockerfile : undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSourceSettings() {
|
private async getSourceSettings() {
|
||||||
@@ -140,6 +195,11 @@ export class LauncherService {
|
|||||||
LAUNCHER_SOURCE_SETTING_KEYS.repository,
|
LAUNCHER_SOURCE_SETTING_KEYS.repository,
|
||||||
LAUNCHER_SOURCE_SETTING_KEYS.branch,
|
LAUNCHER_SOURCE_SETTING_KEYS.branch,
|
||||||
LAUNCHER_SOURCE_SETTING_KEYS.commitSha,
|
LAUNCHER_SOURCE_SETTING_KEYS.commitSha,
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendRepository,
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendBranch,
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendCommitSha,
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendImage,
|
||||||
|
LAUNCHER_SOURCE_SETTING_KEYS.backendDockerfile,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -148,11 +208,28 @@ export class LauncherService {
|
|||||||
const repository = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.repository) || null;
|
const repository = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.repository) || null;
|
||||||
const branch = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.branch) || 'release';
|
const branch = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.branch) || 'release';
|
||||||
const commitSha = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.commitSha) || null;
|
const commitSha = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.commitSha) || null;
|
||||||
|
const backendRepository = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.backendRepository) || null;
|
||||||
|
const backendBranch = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.backendBranch) || 'release';
|
||||||
|
const backendCommitSha = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.backendCommitSha) || null;
|
||||||
|
const backendImage = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.backendImage) || this.configService.get<string>('LAUNCHER_BACKEND_IMAGE', 'localhost:5000/studio-launcher-be');
|
||||||
|
const backendDockerfile = byKey.get(LAUNCHER_SOURCE_SETTING_KEYS.backendDockerfile) || 'Dockerfile';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
repository,
|
repository,
|
||||||
branch,
|
branch,
|
||||||
commitSha,
|
commitSha,
|
||||||
|
client: {
|
||||||
|
repository,
|
||||||
|
branch,
|
||||||
|
commitSha,
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
repository: backendRepository,
|
||||||
|
branch: backendBranch,
|
||||||
|
commitSha: backendCommitSha,
|
||||||
|
image: backendImage,
|
||||||
|
dockerfile: backendDockerfile,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,27 +767,37 @@ export class LauncherService {
|
|||||||
if (!body.version?.trim()) throw new BadRequestException('버전을 입력하세요.');
|
if (!body.version?.trim()) throw new BadRequestException('버전을 입력하세요.');
|
||||||
if (!body.artifact?.trim()) throw new BadRequestException('산출물 파일명을 입력하세요.');
|
if (!body.artifact?.trim()) throw new BadRequestException('산출물 파일명을 입력하세요.');
|
||||||
const source = await this.getSourceSettings();
|
const source = await this.getSourceSettings();
|
||||||
const repository = body.repository ? normalizeRepository(body.repository) : source.repository;
|
const target = body.target?.trim() || 'launcher-client';
|
||||||
const branch = body.branch?.trim() || source.branch || 'release';
|
const isBackend = target === 'launcher-backend';
|
||||||
const commitSha = body.commitSha?.trim() || source.commitSha || null;
|
const repository = body.repository
|
||||||
const plan = this.createLauncherArtifactPlan(body.version.trim(), branch, commitSha, body.artifact.trim(), body.uploadPath?.trim(), body.downloadUrl?.trim());
|
? normalizeRepository(body.repository)
|
||||||
if (!plan.uploadPath) throw new BadRequestException('업로드 경로를 입력하세요.');
|
: isBackend
|
||||||
if (!plan.downloadUrl) throw new BadRequestException('다운로드 URL을 입력하세요.');
|
? source.backend.repository
|
||||||
|
: source.repository;
|
||||||
|
const branch = body.branch?.trim() || (isBackend ? source.backend.branch : source.branch) || 'release';
|
||||||
|
const commitSha = body.commitSha?.trim() || (isBackend ? source.backend.commitSha : source.commitSha) || null;
|
||||||
|
const plan = isBackend
|
||||||
|
? this.createLauncherBackendImagePlan(body.version.trim(), body.artifact.trim(), body.uploadPath?.trim(), body.downloadUrl?.trim())
|
||||||
|
: this.createLauncherArtifactPlan(body.version.trim(), branch, commitSha, body.artifact.trim(), body.uploadPath?.trim(), body.downloadUrl?.trim());
|
||||||
|
if (!repository) throw new BadRequestException('소스 저장소가 연결되지 않았습니다.');
|
||||||
|
if (!plan.uploadPath) throw new BadRequestException(isBackend ? 'Dockerfile 경로를 입력하세요.' : '업로드 경로를 입력하세요.');
|
||||||
|
if (!plan.downloadUrl) throw new BadRequestException(isBackend ? '이미지 태그를 입력하세요.' : '다운로드 URL을 입력하세요.');
|
||||||
|
|
||||||
const logs = [
|
const logs = [
|
||||||
`[queued] launcher build created: ${body.artifact.trim()}`,
|
`[queued] launcher build created: ${body.artifact.trim()}`,
|
||||||
|
`[target] ${target}`,
|
||||||
repository ? `[plan] clone ${this.toCloneUrl(repository)}` : '[plan] source repository is not linked',
|
repository ? `[plan] clone ${this.toCloneUrl(repository)}` : '[plan] source repository is not linked',
|
||||||
`[plan] checkout branch ${branch}`,
|
`[plan] checkout branch ${branch}`,
|
||||||
commitSha ? `[plan] checkout commit ${commitSha}` : '[plan] use branch latest commit',
|
commitSha ? `[plan] checkout commit ${commitSha}` : '[plan] use branch latest commit',
|
||||||
`[plan] build deb package`,
|
isBackend ? `[plan] build and push image ${plan.downloadUrl}` : `[plan] build deb package`,
|
||||||
`[plan] upload ${body.artifact.trim()} to ${plan.uploadPath}`,
|
isBackend ? `[plan] dockerfile ${plan.uploadPath}` : `[plan] upload ${body.artifact.trim()} to ${plan.uploadPath}`,
|
||||||
`[plan] publish download url ${plan.downloadUrl}`,
|
isBackend ? `[plan] image tag ${plan.downloadUrl}` : `[plan] publish download url ${plan.downloadUrl}`,
|
||||||
'[pending] waiting for launcher build runner',
|
'[pending] waiting for launcher build runner',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
const job = await this.prisma.launcherBuildJob.create({
|
const job = await this.prisma.launcherBuildJob.create({
|
||||||
data: {
|
data: {
|
||||||
target: body.target?.trim() || 'launcher-client',
|
target,
|
||||||
repository,
|
repository,
|
||||||
branch,
|
branch,
|
||||||
commitSha,
|
commitSha,
|
||||||
@@ -783,6 +870,30 @@ export class LauncherService {
|
|||||||
await runCommand(`git checkout ${quote(job.commitSha)}`, workDir, append);
|
await runCommand(`git checkout ${quote(job.commitSha)}`, workDir, append);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (job.target === 'launcher-backend') {
|
||||||
|
const dockerfile = job.uploadPath || 'Dockerfile';
|
||||||
|
const imageTag = job.downloadUrl || job.artifact;
|
||||||
|
await append(`[backend] dockerfile ${dockerfile}`);
|
||||||
|
await append(`[backend] image ${imageTag}`);
|
||||||
|
await runCommand(
|
||||||
|
`docker build -t ${quote(imageTag)} -f ${quote(dockerfile)} .`,
|
||||||
|
workDir,
|
||||||
|
append,
|
||||||
|
);
|
||||||
|
await runCommand(`docker push ${quote(imageTag)}`, workDir, append);
|
||||||
|
await append('[done] launcher backend image build completed');
|
||||||
|
|
||||||
|
await this.prisma.launcherBuildJob.update({
|
||||||
|
where: { id: jobId },
|
||||||
|
data: {
|
||||||
|
status: 'SUCCEEDED',
|
||||||
|
finishedAt: new Date(),
|
||||||
|
logs,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const buildCommand = process.env.LAUNCHER_BUILD_COMMAND || 'npm ci && npm run build:linux';
|
const buildCommand = process.env.LAUNCHER_BUILD_COMMAND || 'npm ci && npm run build:linux';
|
||||||
await runCommand(buildCommand, workDir, append);
|
await runCommand(buildCommand, workDir, append);
|
||||||
|
|
||||||
@@ -848,6 +959,23 @@ export class LauncherService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createLauncherBackendImagePlan(
|
||||||
|
version: string,
|
||||||
|
artifact: string,
|
||||||
|
dockerfile?: string,
|
||||||
|
imageTag?: string,
|
||||||
|
) {
|
||||||
|
const registry = this.configService.get<string>('BUILD_REGISTRY', 'localhost:5000').replace(/\/$/, '');
|
||||||
|
const imageName = artifact.includes(':')
|
||||||
|
? artifact
|
||||||
|
: `${registry}/${artifact.replace(/^\/+/, '')}:${version}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
uploadPath: dockerfile || 'Dockerfile',
|
||||||
|
downloadUrl: imageTag || imageName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async uploadLauncherArtifactIfConfigured(
|
private async uploadLauncherArtifactIfConfigured(
|
||||||
job: {
|
job: {
|
||||||
version: string;
|
version: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user