From a55d445de56a4ad58ca9f0a7f1a00da5e8926a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=98=81?= <80161826+dolong2@users.noreply.github.com> Date: Fri, 5 Jun 2026 00:38:55 +0000 Subject: [PATCH 1/3] =?UTF-8?q?[refac]=20=EC=95=A0=ED=94=8C=EB=A6=AC?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=85=98=20=EB=8F=84=EC=BB=A4=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=82=B4=EC=9A=A9=EC=9D=84=20infrastructure=20?= =?UTF-8?q?=EA=B3=84=EC=B8=B5=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/core/common/file/FileContent.kt | 124 ---------------- .../application/file/ImageFileContent.kt | 132 ++++++++++++++++++ 2 files changed, 132 insertions(+), 124 deletions(-) create mode 100644 src/main/kotlin/com/dcd/server/infrastructure/domain/application/file/ImageFileContent.kt diff --git a/src/main/kotlin/com/dcd/server/core/common/file/FileContent.kt b/src/main/kotlin/com/dcd/server/core/common/file/FileContent.kt index fb338fe7..a7d534f0 100644 --- a/src/main/kotlin/com/dcd/server/core/common/file/FileContent.kt +++ b/src/main/kotlin/com/dcd/server/core/common/file/FileContent.kt @@ -5,115 +5,7 @@ import com.dcd.server.core.domain.application.model.enums.ApplicationType import java.lang.StringBuilder object FileContent { - fun getApplicationDockerFileContent( - applicationType: ApplicationType, - version: String, - port: Int, - env: Map, - initialScripts: List - ): String = - when(applicationType) { - ApplicationType.SPRING_BOOT -> getSpringBootDockerFileContent(version, port, env, initialScripts) - ApplicationType.NEST_JS -> getNestJsDockerFileContent(version, port, env, initialScripts) - ApplicationType.GIN -> getGinDockerFileContent(version, port, env, initialScripts) - ApplicationType.MYSQL -> getMYSQLDockerFileContent(version, port, env, initialScripts) - ApplicationType.MARIA_DB -> getMARIADBDockerFileContent(version, port, env, initialScripts) - ApplicationType.H2_DB -> getH2DBDockerFileContent(version, port, env, initialScripts) - ApplicationType.REDIS -> getRedisDockerFileContent(version, port, env, initialScripts) - } - - private fun getSpringBootDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = - """ - FROM amazoncorretto:${version} AS builder - WORKDIR /builder - COPY . . - RUN chmod +x ./gradlew && ./gradlew bootJar - RUN rm -f build/libs/*-plain.jar && mv build/libs/*.jar build/libs/app.jar - - FROM amazoncorretto:${version}-alpine - WORKDIR /app - COPY --from=builder /builder/build/libs/app.jar app.jar - EXPOSE $port - ${getEnvString(env)} - ${getInitialScriptsString(initialScripts)} - CMD ["java", "-jar", "app.jar"] - """.trimIndent() - - private fun getNestJsDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = - """ - FROM node:${version} AS builder - WORKDIR /builder - COPY package*.json ./ - RUN npm ci - COPY . . - RUN npm run build - FROM node:${version}-alpine - WORKDIR /app - ${getEnvString(env)} - ${getInitialScriptsString(initialScripts)} - COPY package*.json ./ - RUN npm ci --production=true - COPY --from=builder /builder/dist ./dist - EXPOSE $port - CMD ["sh", "-c", "TZ=Asia/Seoul node dist/main.js"] - """.trimIndent() - - private fun getGinDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = - """ - FROM golang:${version} AS builder - WORKDIR /builder - COPY go.mod go.sum ./ - RUN go mod download - COPY . . - RUN go mod tidy - RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . - - FROM golang:${version}-alpine - WORKDIR /app - RUN apk --no-cache add ca-certificates tzdata - ${getEnvString(env)} - ${getInitialScriptsString(initialScripts)} - COPY --from=builder /builder/main . - EXPOSE $port - CMD ["./main"] - """.trimIndent() - - private fun getMYSQLDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = - """ - FROM mysql:${version} - WORKDIR /app - EXPOSE $port - ${getEnvString(env)} - ${getInitialScriptsString(initialScripts)} - """.trimIndent() - - private fun getMARIADBDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = - """ - FROM mariadb:${version} - WORKDIR /app - EXPOSE $port - ${getEnvString(env)} - ${getInitialScriptsString(initialScripts)} - """.trimIndent() - - private fun getRedisDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = - """ - FROM redis:${version} - WORKDIR /app - EXPOSE $port - ${getEnvString(env)} - ${getInitialScriptsString(initialScripts)} - """.trimIndent() - - private fun getH2DBDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = - """ - FROM oscarfonts/h2:${version} - WORKDIR /app - EXPOSE $port - ${getEnvString(env)} - ${getInitialScriptsString(initialScripts)} - """.trimIndent() fun getApplicationHttpConfig(application: Application, domain: String): String = """ @@ -136,20 +28,4 @@ object FileContent { } } """.trimIndent() - - private fun getEnvString(env: Map): String { - val envString = StringBuilder() - for (it in env) { - envString.append("ENV ${it.key}=${it.value}\n") - } - return envString.toString() - } - - private fun getInitialScriptsString(initialScripts: List): String { - val initialScriptString = StringBuilder() - for (initialScript in initialScripts) { - initialScriptString.append("RUN $initialScript\n") - } - return initialScriptString.toString() - } } \ No newline at end of file diff --git a/src/main/kotlin/com/dcd/server/infrastructure/domain/application/file/ImageFileContent.kt b/src/main/kotlin/com/dcd/server/infrastructure/domain/application/file/ImageFileContent.kt new file mode 100644 index 00000000..bbd1c7a4 --- /dev/null +++ b/src/main/kotlin/com/dcd/server/infrastructure/domain/application/file/ImageFileContent.kt @@ -0,0 +1,132 @@ +package com.dcd.server.infrastructure.domain.application.file + +import com.dcd.server.core.domain.application.model.enums.ApplicationType +import com.dcd.server.core.domain.application.model.Application + +object ImageFileContent { + fun getApplicationDockerFileContent( + applicationType: ApplicationType, + version: String, + port: Int, + env: Map, + initialScripts: List + ): String = + when(applicationType) { + ApplicationType.SPRING_BOOT -> getSpringBootDockerFileContent(version, port, env, initialScripts) + ApplicationType.NEST_JS -> getNestJsDockerFileContent(version, port, env, initialScripts) + ApplicationType.GIN -> getGinDockerFileContent(version, port, env, initialScripts) + ApplicationType.MYSQL -> getMYSQLDockerFileContent(version, port, env, initialScripts) + ApplicationType.MARIA_DB -> getMARIADBDockerFileContent(version, port, env, initialScripts) + ApplicationType.H2_DB -> getH2DBDockerFileContent(version, port, env, initialScripts) + ApplicationType.REDIS -> getRedisDockerFileContent(version, port, env, initialScripts) + } + + private fun getSpringBootDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = + """ + FROM amazoncorretto:${version} AS builder + WORKDIR /builder + COPY . . + RUN chmod +x ./gradlew && ./gradlew bootJar + RUN rm -f build/libs/*-plain.jar && mv build/libs/*.jar build/libs/app.jar + + FROM amazoncorretto:${version}-alpine + WORKDIR /app + COPY --from=builder /builder/build/libs/app.jar app.jar + EXPOSE $port + ${getEnvString(env)} + ${getInitialScriptsString(initialScripts)} + CMD ["java", "-jar", "app.jar"] + """.trimIndent() + + private fun getNestJsDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = + """ + FROM node:${version} AS builder + WORKDIR /builder + COPY package*.json ./ + RUN npm ci + COPY . . + RUN npm run build + + FROM node:${version}-alpine + WORKDIR /app + ${getEnvString(env)} + ${getInitialScriptsString(initialScripts)} + COPY package*.json ./ + RUN npm ci --production=true + COPY --from=builder /builder/dist ./dist + EXPOSE $port + CMD ["sh", "-c", "TZ=Asia/Seoul node dist/main.js"] + """.trimIndent() + + private fun getGinDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = + """ + FROM golang:${version} AS builder + WORKDIR /builder + COPY go.mod go.sum ./ + RUN go mod download + COPY . . + RUN go mod tidy + RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + + FROM golang:${version}-alpine + WORKDIR /app + RUN apk --no-cache add ca-certificates tzdata + ${getEnvString(env)} + ${getInitialScriptsString(initialScripts)} + COPY --from=builder /builder/main . + EXPOSE $port + CMD ["./main"] + """.trimIndent() + + private fun getMYSQLDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = + """ + FROM mysql:${version} + WORKDIR /app + EXPOSE $port + ${getEnvString(env)} + ${getInitialScriptsString(initialScripts)} + """.trimIndent() + + private fun getMARIADBDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = + """ + FROM mariadb:${version} + WORKDIR /app + EXPOSE $port + ${getEnvString(env)} + ${getInitialScriptsString(initialScripts)} + """.trimIndent() + + private fun getRedisDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = + """ + FROM redis:${version} + WORKDIR /app + EXPOSE $port + ${getEnvString(env)} + ${getInitialScriptsString(initialScripts)} + """.trimIndent() + + private fun getH2DBDockerFileContent(version: String, port: Int, env: Map, initialScripts: List): String = + """ + FROM oscarfonts/h2:${version} + WORKDIR /app + EXPOSE $port + ${getEnvString(env)} + ${getInitialScriptsString(initialScripts)} + """.trimIndent() + + private fun getEnvString(env: Map): String { + val envString = StringBuilder() + for (it in env) { + envString.append("ENV ${it.key}=${it.value}\n") + } + return envString.toString() + } + + private fun getInitialScriptsString(initialScripts: List): String { + val initialScriptString = StringBuilder() + for (initialScript in initialScripts) { + initialScriptString.append("RUN $initialScript\n") + } + return initialScriptString.toString() + } +} \ No newline at end of file From fac15293464a001649af26c2fd6d23d41492833c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=98=81?= <80161826+dolong2@users.noreply.github.com> Date: Fri, 5 Jun 2026 00:46:06 +0000 Subject: [PATCH 2/3] =?UTF-8?q?[refac]=20=EA=B8=B0=EC=A1=B4=20CreateImageF?= =?UTF-8?q?ileService=EB=A5=BC=20ApplicationImageFilePort=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CreateImageFileService.kt | 8 ------ .../spi/ApplicationImageFilePort.kt | 7 +++++ .../adapter/ApplicationImageFileAdapter.kt} | 27 +++++++------------ 3 files changed, 16 insertions(+), 26 deletions(-) delete mode 100644 src/main/kotlin/com/dcd/server/core/domain/application/service/CreateImageFileService.kt create mode 100644 src/main/kotlin/com/dcd/server/core/domain/application/spi/ApplicationImageFilePort.kt rename src/main/kotlin/com/dcd/server/{core/domain/application/service/impl/CreateDockerFileServiceImpl.kt => infrastructure/domain/application/adapter/ApplicationImageFileAdapter.kt} (81%) diff --git a/src/main/kotlin/com/dcd/server/core/domain/application/service/CreateImageFileService.kt b/src/main/kotlin/com/dcd/server/core/domain/application/service/CreateImageFileService.kt deleted file mode 100644 index 7c72320b..00000000 --- a/src/main/kotlin/com/dcd/server/core/domain/application/service/CreateImageFileService.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.dcd.server.core.domain.application.service - -import com.dcd.server.core.domain.application.model.Application - -interface CreateImageFileService { - suspend fun createFileByApplicationId(id: String) - suspend fun createFileToApplication(application: Application) -} diff --git a/src/main/kotlin/com/dcd/server/core/domain/application/spi/ApplicationImageFilePort.kt b/src/main/kotlin/com/dcd/server/core/domain/application/spi/ApplicationImageFilePort.kt new file mode 100644 index 00000000..44905214 --- /dev/null +++ b/src/main/kotlin/com/dcd/server/core/domain/application/spi/ApplicationImageFilePort.kt @@ -0,0 +1,7 @@ +package com.dcd.server.core.domain.application.spi + +import com.dcd.server.core.domain.application.model.Application + +interface ApplicationImageFilePort { + suspend fun createImageFile(application: Application) +} diff --git a/src/main/kotlin/com/dcd/server/core/domain/application/service/impl/CreateDockerFileServiceImpl.kt b/src/main/kotlin/com/dcd/server/infrastructure/domain/application/adapter/ApplicationImageFileAdapter.kt similarity index 81% rename from src/main/kotlin/com/dcd/server/core/domain/application/service/impl/CreateDockerFileServiceImpl.kt rename to src/main/kotlin/com/dcd/server/infrastructure/domain/application/adapter/ApplicationImageFileAdapter.kt index b6866e4b..7e9a5223 100644 --- a/src/main/kotlin/com/dcd/server/core/domain/application/service/impl/CreateDockerFileServiceImpl.kt +++ b/src/main/kotlin/com/dcd/server/infrastructure/domain/application/adapter/ApplicationImageFileAdapter.kt @@ -1,4 +1,4 @@ -package com.dcd.server.core.domain.application.service.impl +package com.dcd.server.infrastructure.domain.application.adapter import com.dcd.server.core.common.file.exception.FileOperationException import com.dcd.server.core.common.file.spi.FileOperationPort @@ -7,40 +7,31 @@ import com.dcd.server.core.domain.application.event.ChangeApplicationStatusEvent import com.dcd.server.core.domain.application.exception.ApplicationNotFoundException import com.dcd.server.core.domain.application.model.Application import com.dcd.server.core.domain.application.model.enums.ApplicationStatus -import com.dcd.server.core.domain.application.service.CreateImageFileService +import com.dcd.server.core.domain.application.spi.ApplicationImageFilePort import com.dcd.server.core.domain.application.spi.QueryApplicationInitialScriptPort import com.dcd.server.core.domain.application.spi.QueryApplicationPort import com.dcd.server.core.domain.application.util.FailureCase -import com.dcd.server.core.common.file.FileContent import com.dcd.server.core.domain.env.spi.QueryApplicationEnvPort +import com.dcd.server.infrastructure.domain.application.file.ImageFileContent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.withContext import org.springframework.context.ApplicationEventPublisher -import org.springframework.stereotype.Service +import org.springframework.stereotype.Component import java.io.File import java.io.IOException import java.nio.file.Paths -@Service -class CreateDockerFileServiceImpl( - private val queryApplicationPort: QueryApplicationPort, +@Component +class ApplicationImageFileAdapter( private val queryApplicationEnvPort: QueryApplicationEnvPort, private val queryApplicationInitialScriptPort: QueryApplicationInitialScriptPort, private val fileOperationPort: FileOperationPort, private val eventPublisher: ApplicationEventPublisher, private val encryptPort: EncryptPort -) : CreateImageFileService { - override suspend fun createFileByApplicationId(id: String) { - val application = (queryApplicationPort.findById(id) - ?: throw ApplicationNotFoundException()) - withContext(Dispatchers.IO) { - createFile(application, this) - } - } - - override suspend fun createFileToApplication(application: Application) { +) : ApplicationImageFilePort { + override suspend fun createImageFile(application: Application) { withContext(Dispatchers.IO) { createFile(application, this) } @@ -77,7 +68,7 @@ class CreateDockerFileServiceImpl( } val fileContent = - FileContent.getApplicationDockerFileContent( + ImageFileContent.getApplicationDockerFileContent( application.applicationType, version, application.port, From 2ab3e72b47f29bc09f592ebcf5ebd2802ad70688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=98=81?= <80161826+dolong2@users.noreply.github.com> Date: Fri, 5 Jun 2026 05:16:02 +0000 Subject: [PATCH 3/3] =?UTF-8?q?[refac]=20=EA=B8=B0=EC=A1=B4=EC=97=90=20Cre?= =?UTF-8?q?ateImageService=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8D=98?= =?UTF-8?q?=20=EB=B6=80=EB=B6=84=EC=9D=B4=20ApplicationImageFilePort?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listener/ApplicationEventListener.kt | 6 +- .../usecase/CreateApplicationUseCase.kt | 6 +- .../usecase/DeployApplicationUseCase.kt | 6 +- .../CreateDockerFileServiceImplTest.kt | 131 ------- .../usecase/DeployApplicationUseCaseTest.kt | 4 +- .../ApplicationImageFileAdapterTest.kt | 344 ++++++++++++++++++ 6 files changed, 355 insertions(+), 142 deletions(-) delete mode 100644 src/test/kotlin/com/dcd/server/core/domain/application/service/CreateDockerFileServiceImplTest.kt create mode 100644 src/test/kotlin/com/dcd/server/infrastructure/domain/application/adapter/ApplicationImageFileAdapterTest.kt diff --git a/src/main/kotlin/com/dcd/server/core/domain/application/event/listener/ApplicationEventListener.kt b/src/main/kotlin/com/dcd/server/core/domain/application/event/listener/ApplicationEventListener.kt index c17f56d9..a39ea2c9 100644 --- a/src/main/kotlin/com/dcd/server/core/domain/application/event/listener/ApplicationEventListener.kt +++ b/src/main/kotlin/com/dcd/server/core/domain/application/event/listener/ApplicationEventListener.kt @@ -6,9 +6,9 @@ import com.dcd.server.core.domain.application.event.DeployApplicationEvent import com.dcd.server.core.domain.application.model.DeploymentResult import com.dcd.server.core.domain.application.model.enums.ApplicationStatus import com.dcd.server.core.domain.application.model.enums.ApplicationType -import com.dcd.server.core.domain.application.service.CreateImageFileService import com.dcd.server.core.domain.application.service.DeleteApplicationDirectoryService import com.dcd.server.core.domain.application.spi.ApplicationRemoteRepoPort +import com.dcd.server.core.domain.application.spi.ApplicationImageFilePort import com.dcd.server.core.domain.application.spi.CommandApplicationPort import com.dcd.server.core.domain.application.spi.QueryApplicationPort import com.dcd.server.core.domain.volume.spi.QueryVolumePort @@ -25,7 +25,7 @@ import org.springframework.transaction.event.TransactionalEventListener class ApplicationEventListener( private val commandApplicationPort: CommandApplicationPort, private val applicationRemoteRepoPort: ApplicationRemoteRepoPort, - private val createImageFileService: CreateImageFileService, + private val applicationImageFilePort: ApplicationImageFilePort, private val deleteApplicationDirectoryService: DeleteApplicationDirectoryService, private val containerPort: ContainerPort, private val queryApplicationPort: QueryApplicationPort, @@ -60,7 +60,7 @@ class ApplicationEventListener( } else -> {} } - createImageFileService.createFileToApplication(application) + applicationImageFilePort.createImageFile(application) containerPort.execute { buildImage(application, "./${application.name}/Dockerfile") diff --git a/src/main/kotlin/com/dcd/server/core/domain/application/usecase/CreateApplicationUseCase.kt b/src/main/kotlin/com/dcd/server/core/domain/application/usecase/CreateApplicationUseCase.kt index b585a83c..7cf07d27 100644 --- a/src/main/kotlin/com/dcd/server/core/domain/application/usecase/CreateApplicationUseCase.kt +++ b/src/main/kotlin/com/dcd/server/core/domain/application/usecase/CreateApplicationUseCase.kt @@ -8,11 +8,11 @@ import com.dcd.server.core.domain.application.dto.request.CreateApplicationReqDt import com.dcd.server.core.domain.application.dto.response.CreateApplicationResDto import com.dcd.server.core.domain.application.exception.AlreadyExistsApplicationException import com.dcd.server.core.domain.application.model.enums.ApplicationType -import com.dcd.server.core.domain.application.service.CreateImageFileService import com.dcd.server.core.domain.application.service.DeleteApplicationDirectoryService import com.dcd.server.core.domain.application.service.GetExternalPortService import com.dcd.server.core.domain.application.service.InitialScriptService import com.dcd.server.core.domain.application.spi.ApplicationRemoteRepoPort +import com.dcd.server.core.domain.application.spi.ApplicationImageFilePort import com.dcd.server.core.domain.application.spi.CommandApplicationPort import com.dcd.server.core.domain.application.spi.QueryApplicationPort import com.dcd.server.core.domain.env.service.EnvAutoMatchService @@ -30,7 +30,7 @@ class CreateApplicationUseCase( private val containerPort: ContainerPort, private val queryVolumePort: QueryVolumePort, private val applicationRemoteRepoPort: ApplicationRemoteRepoPort, - private val createImageFileService: CreateImageFileService, + private val applicationImageFilePort: ApplicationImageFilePort, private val getExternalPortService: GetExternalPortService, private val deleteApplicationDirectoryService: DeleteApplicationDirectoryService, private val envAutoMatchService: EnvAutoMatchService, @@ -60,7 +60,7 @@ class CreateApplicationUseCase( else -> {} } - createImageFileService.createFileToApplication(application) + applicationImageFilePort.createImageFile(application) containerPort.execute { buildImage(application, "./${application.name}/Dockerfile") diff --git a/src/main/kotlin/com/dcd/server/core/domain/application/usecase/DeployApplicationUseCase.kt b/src/main/kotlin/com/dcd/server/core/domain/application/usecase/DeployApplicationUseCase.kt index 24312b23..1f473abc 100644 --- a/src/main/kotlin/com/dcd/server/core/domain/application/usecase/DeployApplicationUseCase.kt +++ b/src/main/kotlin/com/dcd/server/core/domain/application/usecase/DeployApplicationUseCase.kt @@ -10,9 +10,9 @@ import com.dcd.server.core.domain.application.exception.CanNotDeployApplicationE import com.dcd.server.core.domain.application.model.Application import com.dcd.server.core.domain.application.model.enums.ApplicationStatus import com.dcd.server.core.domain.application.model.enums.ApplicationType -import com.dcd.server.core.domain.application.service.CreateImageFileService import com.dcd.server.core.domain.application.service.DeleteApplicationDirectoryService import com.dcd.server.core.domain.application.spi.ApplicationRemoteRepoPort +import com.dcd.server.core.domain.application.spi.ApplicationImageFilePort import com.dcd.server.core.domain.application.spi.QueryApplicationPort import com.dcd.server.core.domain.volume.spi.QueryVolumePort import com.dcd.server.core.domain.workspace.exception.WorkspaceNotFoundException @@ -26,7 +26,7 @@ class DeployApplicationUseCase( private val containerPort: ContainerPort, private val queryVolumePort: QueryVolumePort, private val applicationRemoteRepoPort: ApplicationRemoteRepoPort, - private val createImageFileService: CreateImageFileService, + private val applicationImageFilePort: ApplicationImageFilePort, private val deleteApplicationDirectoryService: DeleteApplicationDirectoryService, private val lockPort: LockPort, private val eventPublisher: ApplicationEventPublisher, @@ -95,7 +95,7 @@ class DeployApplicationUseCase( } else -> {} } - createImageFileService.createFileToApplication(application) + applicationImageFilePort.createImageFile(application) } buildImage(application, "./${application.name}/Dockerfile") diff --git a/src/test/kotlin/com/dcd/server/core/domain/application/service/CreateDockerFileServiceImplTest.kt b/src/test/kotlin/com/dcd/server/core/domain/application/service/CreateDockerFileServiceImplTest.kt deleted file mode 100644 index 2476dc7e..00000000 --- a/src/test/kotlin/com/dcd/server/core/domain/application/service/CreateDockerFileServiceImplTest.kt +++ /dev/null @@ -1,131 +0,0 @@ -package com.dcd.server.core.domain.application.service - -import com.dcd.server.core.common.file.FileContent -import com.dcd.server.core.common.file.spi.FileOperationPort -import com.dcd.server.core.common.spi.EncryptPort -import com.dcd.server.core.domain.application.model.enums.ApplicationType -import com.dcd.server.core.domain.application.service.impl.CreateDockerFileServiceImpl -import com.dcd.server.core.domain.application.spi.QueryApplicationPort -import com.dcd.server.infrastructure.global.adapter.FileOperationAdapter -import com.dcd.server.core.domain.application.spi.QueryApplicationInitialScriptPort -import com.dcd.server.core.domain.env.spi.QueryApplicationEnvPort -import io.kotest.core.spec.style.BehaviorSpec -import io.kotest.matchers.shouldBe -import io.mockk.every -import io.mockk.mockk -import org.springframework.context.ApplicationEventPublisher -import util.application.ApplicationGenerator -import java.io.File -import java.nio.file.Paths - -class CreateDockerFileServiceImplTest : BehaviorSpec({ - val queryApplicationPort = mockk() - val queryApplicationEnvPort = mockk() - val queryApplicationInitialScriptPort = mockk() - val fileOperationPort = FileOperationAdapter() - val eventPublisher = mockk(relaxed = true) - val encryptPort = mockk() - val createDockerFileService = CreateDockerFileServiceImpl(queryApplicationPort, queryApplicationEnvPort, queryApplicationInitialScriptPort, fileOperationPort, eventPublisher, encryptPort) - - given("스프링 애플리케이션이 주어지고") { - val application = - ApplicationGenerator.generateApplication(applicationType = ApplicationType.SPRING_BOOT) - every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() - every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() - - `when`("서비스를 실행할때") { - createDockerFileService.createFileToApplication(application) - - then("애플리케이션의 이름을 가진 디렉토리가 생성되야함") { - Paths.get(application.name).toFile().exists() shouldBe true - } - then("생성된 DockerFile의 내용은 FileContent의 내용과 같아야함") { - val actualFileContent = StringBuilder() - File("./${application.name}/Dockerfile").forEachLine { - actualFileContent.append(it + "\n") - } - actualFileContent.deleteAt(actualFileContent.length - 1) - - actualFileContent.toString() shouldBe FileContent.getApplicationDockerFileContent(application.applicationType, application.version, application.port, emptyMap(), listOf()) - } - } - - fileOperationPort.deleteDirectory(Paths.get(application.name)) - } - - given("레디스 애플리케이션이 주어지고") { - val application = - ApplicationGenerator.generateApplication(applicationType = ApplicationType.REDIS) - every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() - every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() - - `when`("서비스를 실행할때") { - createDockerFileService.createFileToApplication(application) - - then("애플리케이션의 이름을 가진 디렉토리가 생성되야함") { - Paths.get(application.name).toFile().exists() shouldBe true - } - then("생성된 DockerFile의 내용은 FileContent의 내용과 같아야함") { - val actualFileContent = StringBuilder() - File("./${application.name}/Dockerfile").forEachLine { - actualFileContent.append(it + "\n") - } - - actualFileContent.toString() shouldBe FileContent.getApplicationDockerFileContent(application.applicationType, application.version, application.port, emptyMap(), listOf()) - } - } - - fileOperationPort.deleteDirectory(Paths.get(application.name)) - } - - given("MYSQL 애플리케이션이 주어지고") { - val application = - ApplicationGenerator.generateApplication(applicationType = ApplicationType.MYSQL) - every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() - every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() - - `when`("서비스를 실행할때") { - createDockerFileService.createFileToApplication(application) - - then("애플리케이션의 이름을 가진 디렉토리가 생성되야함") { - Paths.get(application.name).toFile().exists() shouldBe true - } - then("생성된 DockerFile의 내용은 FileContent의 내용과 같아야함") { - val actualFileContent = StringBuilder() - File("./${application.name}/Dockerfile").forEachLine { - actualFileContent.append(it + "\n") - } - - actualFileContent.toString() shouldBe FileContent.getApplicationDockerFileContent(application.applicationType, application.version, application.port, emptyMap(), listOf()) - } - } - - fileOperationPort.deleteDirectory(Paths.get(application.name)) - } - - given("MARIADB 애플리케이션이 주어지고") { - val application = - ApplicationGenerator.generateApplication(applicationType = ApplicationType.MARIA_DB) - every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() - every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() - - `when`("서비스를 실행할때") { - createDockerFileService.createFileToApplication(application) - - then("애플리케이션의 이름을 가진 디렉토리가 생성되야함") { - Paths.get(application.name).toFile().exists() shouldBe true - } - then("생성된 DockerFile의 내용은 FileContent의 내용과 같아야함") { - val actualFileContent = StringBuilder() - File("./${application.name}/Dockerfile").forEachLine { - actualFileContent.append(it + "\n") - } - - actualFileContent.toString() shouldBe FileContent.getApplicationDockerFileContent(application.applicationType, application.version, application.port, emptyMap(), listOf()) - } - } - - fileOperationPort.deleteDirectory(Paths.get(application.name)) - } - -}) \ No newline at end of file diff --git a/src/test/kotlin/com/dcd/server/core/domain/application/usecase/DeployApplicationUseCaseTest.kt b/src/test/kotlin/com/dcd/server/core/domain/application/usecase/DeployApplicationUseCaseTest.kt index d98c1eab..97645ee1 100644 --- a/src/test/kotlin/com/dcd/server/core/domain/application/usecase/DeployApplicationUseCaseTest.kt +++ b/src/test/kotlin/com/dcd/server/core/domain/application/usecase/DeployApplicationUseCaseTest.kt @@ -4,7 +4,7 @@ import com.dcd.server.core.common.spi.ContainerPort import com.dcd.server.core.domain.application.exception.ApplicationNotFoundException import com.dcd.server.core.domain.application.exception.CanNotDeployApplicationException import com.dcd.server.core.domain.application.model.enums.ApplicationStatus -import com.dcd.server.core.domain.application.service.impl.CreateDockerFileServiceImpl +import com.dcd.server.core.domain.application.spi.ApplicationImageFilePort import com.dcd.server.core.domain.application.spi.CommandApplicationPort import com.dcd.server.core.domain.application.spi.QueryApplicationPort import com.dcd.server.core.domain.user.spi.CommandUserPort @@ -32,7 +32,7 @@ class DeployApplicationUseCaseTest( @MockkBean(relaxed = true) private val containerPort: ContainerPort, @MockkBean(relaxUnitFun = true) - private val createImageFileService: CreateDockerFileServiceImpl, + private val applicationImageFilePort: ApplicationImageFilePort, private val commandUserPort: CommandUserPort, private val commandWorkspacePort: CommandWorkspacePort, private val commandApplicationPort: CommandApplicationPort, diff --git a/src/test/kotlin/com/dcd/server/infrastructure/domain/application/adapter/ApplicationImageFileAdapterTest.kt b/src/test/kotlin/com/dcd/server/infrastructure/domain/application/adapter/ApplicationImageFileAdapterTest.kt new file mode 100644 index 00000000..d61120e0 --- /dev/null +++ b/src/test/kotlin/com/dcd/server/infrastructure/domain/application/adapter/ApplicationImageFileAdapterTest.kt @@ -0,0 +1,344 @@ +package com.dcd.server.infrastructure.domain.application.adapter + +import com.dcd.server.core.common.file.spi.FileOperationPort +import com.dcd.server.core.common.spi.EncryptPort +import com.dcd.server.core.domain.application.model.ApplicationInitialScript +import com.dcd.server.core.domain.application.model.enums.ApplicationType +import com.dcd.server.core.domain.application.spi.QueryApplicationInitialScriptPort +import com.dcd.server.core.domain.env.model.ApplicationEnv +import com.dcd.server.core.domain.env.model.ApplicationEnvDetail +import com.dcd.server.core.domain.env.spi.QueryApplicationEnvPort +import com.dcd.server.infrastructure.domain.application.file.ImageFileContent +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.springframework.context.ApplicationEventPublisher +import util.application.ApplicationGenerator +import java.nio.file.Paths +import java.util.UUID + +class ApplicationImageFileAdapterTest : BehaviorSpec({ + val queryApplicationEnvPort = mockk() + val queryApplicationInitialScriptPort = mockk() + val fileOperationPort = mockk() + val eventPublisher = mockk(relaxed = true) + val encryptPort = mockk() + val adapter = ApplicationImageFileAdapter( + queryApplicationEnvPort, + queryApplicationInitialScriptPort, + fileOperationPort, + eventPublisher, + encryptPort + ) + + given("SPRING_BOOT 애플리케이션이 주어지고") { + val application = ApplicationGenerator.generateApplication(applicationType = ApplicationType.SPRING_BOOT) + every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() + every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() + every { fileOperationPort.createDirectory(any()) } returns Unit + every { fileOperationPort.writeFile(any(), any()) } returns Unit + + `when`("이미지 파일을 생성할때") { + runBlocking { + adapter.createImageFile(application) + } + + then("도커파일 내용이 SPRING_BOOT용으로 생성되어야함") { + val expectedContent = ImageFileContent.getApplicationDockerFileContent( + application.applicationType, + application.version, + application.port, + emptyMap(), + listOf() + ) + + verify { + fileOperationPort.writeFile( + Paths.get(application.name, "Dockerfile"), + expectedContent + ) + } + } + } + } + + given("NEST_JS 애플리케이션이 주어지고") { + val application = ApplicationGenerator.generateApplication(applicationType = ApplicationType.NEST_JS) + every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() + every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() + every { fileOperationPort.createDirectory(any()) } returns Unit + every { fileOperationPort.writeFile(any(), any()) } returns Unit + + `when`("이미지 파일을 생성할때") { + runBlocking { + adapter.createImageFile(application) + } + + then("도커파일 내용이 NEST_JS용으로 생성되어야함") { + val expectedContent = ImageFileContent.getApplicationDockerFileContent( + application.applicationType, + application.version, + application.port, + emptyMap(), + listOf() + ) + + verify { + fileOperationPort.writeFile( + Paths.get(application.name, "Dockerfile"), + expectedContent + ) + } + } + } + } + + given("GIN 애플리케이션이 주어지고") { + val application = ApplicationGenerator.generateApplication(applicationType = ApplicationType.GIN) + every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() + every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() + every { fileOperationPort.createDirectory(any()) } returns Unit + every { fileOperationPort.writeFile(any(), any()) } returns Unit + + `when`("이미지 파일을 생성할때") { + runBlocking { + adapter.createImageFile(application) + } + + then("도커파일 내용이 GIN용으로 생성되어야함") { + val expectedContent = ImageFileContent.getApplicationDockerFileContent( + application.applicationType, + application.version, + application.port, + emptyMap(), + listOf() + ) + + verify { + fileOperationPort.writeFile( + Paths.get(application.name, "Dockerfile"), + expectedContent + ) + } + } + } + } + + given("MYSQL 애플리케이션이 주어지고") { + val application = ApplicationGenerator.generateApplication(applicationType = ApplicationType.MYSQL) + every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() + every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() + every { fileOperationPort.createDirectory(any()) } returns Unit + every { fileOperationPort.writeFile(any(), any()) } returns Unit + + `when`("이미지 파일을 생성할때") { + runBlocking { + adapter.createImageFile(application) + } + + then("도커파일 내용이 MYSQL용으로 생성되어야함") { + val expectedContent = ImageFileContent.getApplicationDockerFileContent( + application.applicationType, + application.version, + application.port, + emptyMap(), + listOf() + ) + + verify { + fileOperationPort.writeFile( + Paths.get(application.name, "Dockerfile"), + expectedContent + ) + } + } + } + } + + given("MARIA_DB 애플리케이션이 주어지고") { + val application = ApplicationGenerator.generateApplication(applicationType = ApplicationType.MARIA_DB) + every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() + every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() + every { fileOperationPort.createDirectory(any()) } returns Unit + every { fileOperationPort.writeFile(any(), any()) } returns Unit + + `when`("이미지 파일을 생성할때") { + runBlocking { + adapter.createImageFile(application) + } + + then("도커파일 내용이 MARIA_DB용으로 생성되어야함") { + val expectedContent = ImageFileContent.getApplicationDockerFileContent( + application.applicationType, + application.version, + application.port, + emptyMap(), + listOf() + ) + + verify { + fileOperationPort.writeFile( + Paths.get(application.name, "Dockerfile"), + expectedContent + ) + } + } + } + } + + given("REDIS 애플리케이션이 주어지고") { + val application = ApplicationGenerator.generateApplication(applicationType = ApplicationType.REDIS) + every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() + every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() + every { fileOperationPort.createDirectory(any()) } returns Unit + every { fileOperationPort.writeFile(any(), any()) } returns Unit + + `when`("이미지 파일을 생성할때") { + runBlocking { + adapter.createImageFile(application) + } + + then("도커파일 내용이 REDIS용으로 생성되어야함") { + val expectedContent = ImageFileContent.getApplicationDockerFileContent( + application.applicationType, + application.version, + application.port, + emptyMap(), + listOf() + ) + + verify { + fileOperationPort.writeFile( + Paths.get(application.name, "Dockerfile"), + expectedContent + ) + } + } + } + } + + given("H2_DB 애플리케이션이 주어지고") { + val application = ApplicationGenerator.generateApplication(applicationType = ApplicationType.H2_DB) + every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() + every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() + every { fileOperationPort.createDirectory(any()) } returns Unit + every { fileOperationPort.writeFile(any(), any()) } returns Unit + + `when`("이미지 파일을 생성할때") { + runBlocking { + adapter.createImageFile(application) + } + + then("도커파일 내용이 H2_DB용으로 생성되어야함") { + val expectedContent = ImageFileContent.getApplicationDockerFileContent( + application.applicationType, + application.version, + application.port, + emptyMap(), + listOf() + ) + + verify { + fileOperationPort.writeFile( + Paths.get(application.name, "Dockerfile"), + expectedContent + ) + } + } + } + } + + given("환경변수가 있는 애플리케이션이 주어지고") { + val application = ApplicationGenerator.generateApplication(applicationType = ApplicationType.SPRING_BOOT) + val envDetails = listOf( + ApplicationEnvDetail(id = UUID.randomUUID(), key = "DATABASE_URL", value = "jdbc:mysql://localhost:3306/db", encryption = false), + ApplicationEnvDetail(id = UUID.randomUUID(), key = "API_KEY", value = "encrypted_value", encryption = true) + ) + val appEnv = ApplicationEnv( + id = UUID.randomUUID(), + name = "testEnv", + description = "test environment", + details = envDetails, + workspace = application.workspace, + labels = emptyList() + ) + every { queryApplicationEnvPort.findByApplication(application) } returns listOf(appEnv) + every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns emptyList() + every { encryptPort.decrypt("encrypted_value") } returns "decrypted_value" + every { fileOperationPort.createDirectory(any()) } returns Unit + every { fileOperationPort.writeFile(any(), any()) } returns Unit + + `when`("이미지 파일을 생성할때") { + runBlocking { + adapter.createImageFile(application) + } + + then("도커파일에 환경변수가 포함되어야함") { + val env = mapOf( + "DATABASE_URL" to "jdbc:mysql://localhost:3306/db", + "API_KEY" to "decrypted_value" + ) + val expectedContent = ImageFileContent.getApplicationDockerFileContent( + application.applicationType, + application.version, + application.port, + env, + listOf() + ) + + verify { + fileOperationPort.writeFile( + Paths.get(application.name, "Dockerfile"), + expectedContent + ) + } + } + } + } + + given("초기 스크립트가 있는 애플리케이션이 주어지고") { + val application = ApplicationGenerator.generateApplication(applicationType = ApplicationType.SPRING_BOOT) + val initialScripts = listOf( + "apt-get update && apt-get install -y curl", + "mkdir -p /app/logs" + ) + val scriptModels = initialScripts.map { script -> + ApplicationInitialScript( + id = UUID.randomUUID(), + script = script, + application = application + ) + } + every { queryApplicationEnvPort.findByApplication(application) } returns emptyList() + every { queryApplicationInitialScriptPort.findAllByApplication(application) } returns scriptModels + every { fileOperationPort.createDirectory(any()) } returns Unit + every { fileOperationPort.writeFile(any(), any()) } returns Unit + + `when`("이미지 파일을 생성할때") { + runBlocking { + adapter.createImageFile(application) + } + + then("도커파일에 초기 스크립트가 포함되어야함") { + val expectedContent = ImageFileContent.getApplicationDockerFileContent( + application.applicationType, + application.version, + application.port, + emptyMap(), + initialScripts + ) + + verify { + fileOperationPort.writeFile( + Paths.get(application.name, "Dockerfile"), + expectedContent + ) + } + } + } + } + +})