From d6703d2356791a10e3acf7bda77a8aeb52011c76 Mon Sep 17 00:00:00 2001 From: hiondal Date: Mon, 3 Mar 2025 16:58:08 +0900 Subject: [PATCH] add cicd --- deployment/Jenkinsfile | 56 +++++- deployment/Jenkinsfile_SonarQube | 186 ------------------ .../member/service/MemberServiceImpl.java | 3 +- 3 files changed, 50 insertions(+), 195 deletions(-) delete mode 100644 deployment/Jenkinsfile_SonarQube diff --git a/deployment/Jenkinsfile b/deployment/Jenkinsfile index d3329df..09874d3 100644 --- a/deployment/Jenkinsfile +++ b/deployment/Jenkinsfile @@ -54,16 +54,16 @@ podTemplate( } } - stage('Build Applications') { + stage('Build Applications & SonarQube Analysis') { container('podman') { sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2' } container('gradle') { def testContainersConfig = '''docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy - docker.host=unix:///run/podman/podman.sock - ryuk.container.privileged=true - testcontainers.reuse.enable=true''' +docker.host=unix:///run/podman/podman.sock +ryuk.container.privileged=true +testcontainers.reuse.enable=true''' sh """ # TestContainers 설정 @@ -71,11 +71,51 @@ podTemplate( echo '${testContainersConfig}' > member/src/test/resources/testcontainers.properties echo '${testContainersConfig}' > mysub-infra/src/test/resources/testcontainers.properties echo '${testContainersConfig}' > recommend/src/test/resources/testcontainers.properties - - # 빌드 실행 - chmod +x gradlew - ./gradlew clean :member:build :mysub-infra:build :recommend:build """ + + // Member 서비스 빌드 및 SonarQube 분석 + withSonarQubeEnv('SonarQube') { + sh """ + chmod +x gradlew + + # 빌드 실행 + ./gradlew :member:build :mysub-infra:build :recommend:build -x test + + # Member 서비스 + ./gradlew :member:test :member:jacocoTestReport :member:sonar \ + -Dsonar.projectKey=lifesub-member \ + -Dsonar.projectName=lifesub-member \ + -Dsonar.java.binaries=build/classes/java/main \ + -Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \ + -Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/** + + # Recommend 서비스 + ./gradlew :recommend:test :recommend:jacocoTestReport :recommend:sonar \ + -Dsonar.projectKey=lifesub-recommend \ + -Dsonar.projectName=lifesub-recommend \ + -Dsonar.java.binaries=build/classes/java/main \ + -Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \ + -Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/** + + # Mysub 서비스 (biz & infra 구조) + ./gradlew :mysub-infra:test :mysub-infra:jacocoTestReport :mysub-infra:sonar \ + -Dsonar.projectKey=lifesub-mysub \ + -Dsonar.projectName=lifesub-mysub \ + -Dsonar.java.binaries=build/classes/java/main \ + -Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \ + -Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/** + """ + } + + } + } + + stage('Quality Gate') { + timeout(time: 10, unit: 'MINUTES') { + def qg = waitForQualityGate() + if (qg.status != 'OK') { + error "Pipeline aborted due to quality gate failure: ${qg.status}" + } } } diff --git a/deployment/Jenkinsfile_SonarQube b/deployment/Jenkinsfile_SonarQube deleted file mode 100644 index 09874d3..0000000 --- a/deployment/Jenkinsfile_SonarQube +++ /dev/null @@ -1,186 +0,0 @@ -def PIPELINE_ID = "${env.BUILD_NUMBER}" - -def getImageTag() { - def dateFormat = new java.text.SimpleDateFormat('yyyyMMddHHmmss') - def currentDate = new Date() - return dateFormat.format(currentDate) -} - -podTemplate( - label: "${PIPELINE_ID}", - serviceAccount: 'jenkins', - containers: [ - containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true), - containerTemplate(name: 'gradle', - image: 'gradle:jdk17', - ttyEnabled: true, - command: 'cat', - envVars: [ - envVar(key: 'DOCKER_HOST', value: 'unix:///run/podman/podman.sock'), - envVar(key: 'TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE', value: '/run/podman/podman.sock'), - envVar(key: 'TESTCONTAINERS_RYUK_DISABLED', value: 'true') - ]), - containerTemplate(name: 'azure-cli', image: 'hiondal/azure-kubectl:latest', command: 'cat', ttyEnabled: true), - containerTemplate(name: 'envsubst', image: "hiondal/envsubst", command: 'sleep', args: '1h') - ], - volumes: [ - emptyDirVolume(mountPath: '/home/gradle/.gradle', memory: false), - emptyDirVolume(mountPath: '/root/.azure', memory: false), - emptyDirVolume(mountPath: '/run/podman', memory: false) - ] -) { - node(PIPELINE_ID) { - def props - def imageTag = getImageTag() - def manifest = "deploy.yaml" - def namespace - def services = ['member', 'mysub', 'recommend'] - - stage("Get Source") { - checkout scm - props = readProperties file: "deployment/deploy_env_vars" - namespace = "${props.namespace}" - } - - stage("Setup AKS") { - container('azure-cli') { - withCredentials([azureServicePrincipal('azure-credentials')]) { - sh """ - az login --service-principal -u \$AZURE_CLIENT_ID -p \$AZURE_CLIENT_SECRET -t \$AZURE_TENANT_ID - az aks get-credentials --resource-group ictcoe-edu --name ${props.teamid}-aks --overwrite-existing - kubectl create namespace ${namespace} --dry-run=client -o yaml | kubectl apply -f - - """ - } - } - } - - stage('Build Applications & SonarQube Analysis') { - container('podman') { - sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2' - } - - container('gradle') { - def testContainersConfig = '''docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy -docker.host=unix:///run/podman/podman.sock -ryuk.container.privileged=true -testcontainers.reuse.enable=true''' - - sh """ - # TestContainers 설정 - mkdir -p member/src/test/resources mysub-infra/src/test/resources recommend/src/test/resources - echo '${testContainersConfig}' > member/src/test/resources/testcontainers.properties - echo '${testContainersConfig}' > mysub-infra/src/test/resources/testcontainers.properties - echo '${testContainersConfig}' > recommend/src/test/resources/testcontainers.properties - """ - - // Member 서비스 빌드 및 SonarQube 분석 - withSonarQubeEnv('SonarQube') { - sh """ - chmod +x gradlew - - # 빌드 실행 - ./gradlew :member:build :mysub-infra:build :recommend:build -x test - - # Member 서비스 - ./gradlew :member:test :member:jacocoTestReport :member:sonar \ - -Dsonar.projectKey=lifesub-member \ - -Dsonar.projectName=lifesub-member \ - -Dsonar.java.binaries=build/classes/java/main \ - -Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \ - -Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/** - - # Recommend 서비스 - ./gradlew :recommend:test :recommend:jacocoTestReport :recommend:sonar \ - -Dsonar.projectKey=lifesub-recommend \ - -Dsonar.projectName=lifesub-recommend \ - -Dsonar.java.binaries=build/classes/java/main \ - -Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \ - -Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/** - - # Mysub 서비스 (biz & infra 구조) - ./gradlew :mysub-infra:test :mysub-infra:jacocoTestReport :mysub-infra:sonar \ - -Dsonar.projectKey=lifesub-mysub \ - -Dsonar.projectName=lifesub-mysub \ - -Dsonar.java.binaries=build/classes/java/main \ - -Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \ - -Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/** - """ - } - - } - } - - stage('Quality Gate') { - timeout(time: 10, unit: 'MINUTES') { - def qg = waitForQualityGate() - if (qg.status != 'OK') { - error "Pipeline aborted due to quality gate failure: ${qg.status}" - } - } - } - - stage('Build & Push Images') { - container('podman') { - withCredentials([usernamePassword( - credentialsId: 'acr-credentials', - usernameVariable: 'USERNAME', - passwordVariable: 'PASSWORD' - )]) { - sh "podman login ${props.registry} --username \$USERNAME --password \$PASSWORD" - - services.each { service -> - def buildDir = service == 'mysub' ? 'mysub-infra' : service - def jarFile = service == 'mysub' ? 'mysub.jar' : "${service}.jar" - - sh """ - podman build \ - --build-arg BUILD_LIB_DIR="${buildDir}/build/libs" \ - --build-arg ARTIFACTORY_FILE="${jarFile}" \ - -f deployment/Dockerfile \ - -t ${props.registry}/${props.image_org}/${service}:${imageTag} . - - podman push ${props.registry}/${props.image_org}/${service}:${imageTag} - """ - } - } - } - } - - stage('Generate & Apply Manifest') { - container('envsubst') { - sh """ - export namespace=${namespace} - export allowed_origins=${props.allowed_origins} - export jwt_secret_key=${props.jwt_secret_key} - export postgres_user=${props.postgres_user} - export postgres_password=${props.postgres_password} - export replicas=${props.replicas} - export resources_requests_cpu=${props.resources_requests_cpu} - export resources_requests_memory=${props.resources_requests_memory} - export resources_limits_cpu=${props.resources_limits_cpu} - export resources_limits_memory=${props.resources_limits_memory} - - # 이미지 경로 환경변수 설정 - export member_image_path=${props.registry}/${props.image_org}/member:${imageTag} - export mysub_image_path=${props.registry}/${props.image_org}/mysub:${imageTag} - export recommend_image_path=${props.registry}/${props.image_org}/recommend:${imageTag} - - # manifest 생성 - envsubst < deployment/${manifest}.template > deployment/${manifest} - cat deployment/${manifest} - """ - } - - container('azure-cli') { - sh """ - kubectl apply -f deployment/${manifest} - - echo "Waiting for deployments to be ready..." - kubectl -n ${namespace} wait --for=condition=available deployment/member --timeout=300s - kubectl -n ${namespace} wait --for=condition=available deployment/mysub --timeout=300s - kubectl -n ${namespace} wait --for=condition=available deployment/recommend --timeout=300s - """ - } - } - } -} \ No newline at end of file diff --git a/member/src/main/java/com/unicorn/lifesub/member/service/MemberServiceImpl.java b/member/src/main/java/com/unicorn/lifesub/member/service/MemberServiceImpl.java index 8c65142..fca8527 100644 --- a/member/src/main/java/com/unicorn/lifesub/member/service/MemberServiceImpl.java +++ b/member/src/main/java/com/unicorn/lifesub/member/service/MemberServiceImpl.java @@ -18,6 +18,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Collection; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -39,7 +40,7 @@ public class MemberServiceImpl implements MemberService { // 사용자의 권한 정보 생성 Collection authorities = member.getRoles().stream() .map(SimpleGrantedAuthority::new) - .toList(); + .collect(Collectors.toList()); return jwtTokenProvider.createToken(member, authorities); }