From dc75bc351e5ad6afbabaa96a58645d8e166da6c2 Mon Sep 17 00:00:00 2001 From: pbccc Date: Thu, 22 Aug 2024 15:35:19 +0900 Subject: [PATCH 1/3] test --- Dockerfile | 3 - README.md | 2 +- .../java/kr/co/mcmp/config/SwaggerConfig.java | 36 ++++++++--- .../exception/AlreadyExistsException.java | 2 +- .../exception/GlobalExceptionHandler.java | 2 +- .../kr/co/mcmp/exception/McmpException.java | 2 +- .../externalrepo/ExternalRepoController.java | 2 +- src/main/java/kr/co/mcmp/util/AES256Util.java | 60 ++++++++++++++++++- 8 files changed, 92 insertions(+), 17 deletions(-) delete mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ffe2f6a..0000000 --- a/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM openjdk:17-slim -COPY ./build/libs/am.jar am.jar -ENTRYPOINT ["java", "-jar","am.jar"] diff --git a/README.md b/README.md index 0f8c547..e5e81d0 100755 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ v.0.3.0(2024.10) ## 목차 -1. [mc-application-manager 실행 및 개발 환경] +1. [mc-application-manager 실행 및 개발 환경] 2. [mc-application-manager실행 방법] 3. [mc-application-manager 소스 빌드 및 실행 방법 상세] 4. [mc-application-manager 기여 방법] diff --git a/src/main/java/kr/co/mcmp/config/SwaggerConfig.java b/src/main/java/kr/co/mcmp/config/SwaggerConfig.java index 4042914..91422f9 100644 --- a/src/main/java/kr/co/mcmp/config/SwaggerConfig.java +++ b/src/main/java/kr/co/mcmp/config/SwaggerConfig.java @@ -8,18 +8,38 @@ import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 -@EnableAutoConfiguration +@OpenAPIDefinition(info = @Info(title = "M-CMP API", version = "v1")) public class SwaggerConfig { + public static final String AUTHORIZATION = "Authorization"; + @Bean - public Docket api() { - return new Docket(DocumentationType.SWAGGER_2) - .select() - .apis(RequestHandlerSelectors.basePackage("kr.co.strato.jmeter.jmeter")) - .paths(PathSelectors.any()) - .build(); + public OpenAPI getApi() { + SecurityRequirement securityRequirement = new SecurityRequirement().addList(AUTHORIZATION); + Components components = new Components() + .addSecuritySchemes(AUTHORIZATION, new SecurityScheme() + .name(AUTHORIZATION) + .in(SecurityScheme.In.HEADER) + .type(SecurityScheme.Type.APIKEY)); + + return new OpenAPI() + .addSecurityItem(securityRequirement) + .components(components); } -} \ No newline at end of file +} diff --git a/src/main/java/kr/co/mcmp/exception/AlreadyExistsException.java b/src/main/java/kr/co/mcmp/exception/AlreadyExistsException.java index 20a0ff6..9e37ccd 100644 --- a/src/main/java/kr/co/mcmp/exception/AlreadyExistsException.java +++ b/src/main/java/kr/co/mcmp/exception/AlreadyExistsException.java @@ -1,6 +1,6 @@ package kr.co.mcmp.exception; -import kr.co.mcmp.api.response.ResponseCode; +import kr.co.mcmp.response.ResponseCode; import lombok.Getter; @Getter diff --git a/src/main/java/kr/co/mcmp/exception/GlobalExceptionHandler.java b/src/main/java/kr/co/mcmp/exception/GlobalExceptionHandler.java index 3c79973..a6eddb7 100644 --- a/src/main/java/kr/co/mcmp/exception/GlobalExceptionHandler.java +++ b/src/main/java/kr/co/mcmp/exception/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ package kr.co.mcmp.exception; -import kr.co.mcmp.api.response.ResponseWrapper; +import kr.co.mcmp.response.ResponseWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; diff --git a/src/main/java/kr/co/mcmp/exception/McmpException.java b/src/main/java/kr/co/mcmp/exception/McmpException.java index 369ea10..430aaf6 100644 --- a/src/main/java/kr/co/mcmp/exception/McmpException.java +++ b/src/main/java/kr/co/mcmp/exception/McmpException.java @@ -1,6 +1,6 @@ package kr.co.mcmp.exception; -import kr.co.mcmp.api.response.ResponseCode; +import kr.co.mcmp.response.ResponseCode; import lombok.Getter; @Getter diff --git a/src/main/java/kr/co/mcmp/externalrepo/ExternalRepoController.java b/src/main/java/kr/co/mcmp/externalrepo/ExternalRepoController.java index 92a7ce3..d64fdb8 100644 --- a/src/main/java/kr/co/mcmp/externalrepo/ExternalRepoController.java +++ b/src/main/java/kr/co/mcmp/externalrepo/ExternalRepoController.java @@ -2,9 +2,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import kr.co.mcmp.api.response.ResponseWrapper; import kr.co.mcmp.externalrepo.model.ArtifactHubPackage; import kr.co.mcmp.externalrepo.model.DockerHubCatalog; +import kr.co.mcmp.response.ResponseWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/kr/co/mcmp/util/AES256Util.java b/src/main/java/kr/co/mcmp/util/AES256Util.java index 66ed95d..d35595a 100644 --- a/src/main/java/kr/co/mcmp/util/AES256Util.java +++ b/src/main/java/kr/co/mcmp/util/AES256Util.java @@ -2,6 +2,7 @@ import org.apache.commons.codec.binary.Base64; +import org.springframework.beans.factory.annotation.Value; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; @@ -15,7 +16,13 @@ public class AES256Util { private static String iv; private static Key keySpec; - + private static String authAesKey; + + @Value("${aes.key}") + public void setAuthAesKey(String aesKey) { + authAesKey = aesKey; + } + public AES256Util(String authAesKey) throws UnsupportedEncodingException { this.iv = authAesKey.substring(0, 16); byte[] keyBytes = new byte[16]; @@ -46,4 +53,55 @@ public static String decrypt(String str) throws NoSuchAlgorithmException, byte[] byteStr = Base64.decodeBase64(str.getBytes()); return new String(c.doFinal(byteStr), "UTF-8"); } + + public static String encryptOssPassword(String str) { + String enStr = null; + + try { + String iv = authAesKey.substring(0, 16); + byte[] keyBytes = new byte[16]; + byte[] b = authAesKey.getBytes("UTF-8"); + int len = b.length; + if (len > keyBytes.length) { + len = keyBytes.length; + } + System.arraycopy(b, 0, keyBytes, 0, len); + SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); + + Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); + c.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes())); + byte[] encrypted = c.doFinal(str.getBytes("UTF-8")); + enStr = new String(Base64.encodeBase64(encrypted)); + + } catch (GeneralSecurityException | UnsupportedEncodingException e ) { + return null; + } + + return enStr; + } + + public static String decryptOssPassword(String str){ + String deStr = null; + + try { + String iv = authAesKey.substring(0, 16); + byte[] keyBytes = new byte[16]; + byte[] b = authAesKey.getBytes("UTF-8"); + int len = b.length; + if (len > keyBytes.length) { + len = keyBytes.length; + } + System.arraycopy(b, 0, keyBytes, 0, len); + SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); + + Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); + c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes())); + byte[] byteStr = Base64.decodeBase64(str.getBytes()); + deStr = new String(c.doFinal(byteStr), "UTF-8"); + }catch (GeneralSecurityException | UnsupportedEncodingException e ) { + return null; + } + + return deStr; + } } From 218477e027ab0bed47e38d715a033169e3357a63 Mon Sep 17 00:00:00 2001 From: pbccc Date: Thu, 22 Aug 2024 15:40:36 +0900 Subject: [PATCH 2/3] test --- .../mcmp/config/oss/RestTemplateConfig.java | 18 ++ .../mcmp/config/oss/RestTemplateProvider.java | 22 ++ .../kr/co/mcmp/dto/oss/NexusFormatType.java | 20 ++ .../co/mcmp/dto/oss/NexusRepositoryDto.java | 193 +++++++++++++ .../mcmp/exception/NexusClientException.java | 35 +++ .../co/mcmp/oss/controller/OssController.java | 85 ++++++ .../oss/controller/OssTypeController.java | 56 ++++ src/main/java/kr/co/mcmp/oss/dto/OssDto.java | 90 ++++++ .../java/kr/co/mcmp/oss/dto/OssTypeDto.java | 42 +++ src/main/java/kr/co/mcmp/oss/entity/Oss.java | 38 +++ .../java/kr/co/mcmp/oss/entity/OssType.java | 24 ++ .../mcmp/oss/nexus/api/NexusRestClient.java | 217 +++++++++++++++ .../co/mcmp/oss/nexus/api/NexusStatusApi.java | 25 ++ .../oss/nexus/exception/NexusException.java | 32 +++ .../mcmp/oss/nexus/model/NexusComponent.java | 23 ++ .../oss/nexus/model/NexusPageComponent.java | 19 ++ .../mcmp/oss/nexus/model/NexusRepository.java | 22 ++ .../mcmp/oss/nexus/service/NexusService.java | 38 +++ .../co/mcmp/oss/repository/OssRepository.java | 22 ++ .../oss/repository/OssTypeRepository.java | 16 ++ .../kr/co/mcmp/oss/service/OssService.java | 20 ++ .../co/mcmp/oss/service/OssServiceImpl.java | 256 ++++++++++++++++++ .../co/mcmp/oss/service/OssTypeService.java | 17 ++ .../mcmp/oss/service/OssTypeServiceImpl.java | 86 ++++++ .../kr/co/mcmp/response/ResponseCode.java | 74 +++++ .../kr/co/mcmp/response/ResponseWrapper.java | 42 +++ .../mcmp/service/oss/NexusAdapterFactory.java | 25 ++ .../mcmp/service/oss/NexusAdapterService.java | 12 + .../oss/NexusDockerAdapterService.java | 36 +++ .../service/oss/NexusHelmAdapterService.java | 37 +++ .../service/oss/NexusRawAdapterService.java | 34 +++ .../oss/NexusRepositoryAdapterClient.java | 190 +++++++++++++ .../service/oss/NexusRepositoryService.java | 38 +++ 33 files changed, 1904 insertions(+) create mode 100644 src/main/java/kr/co/mcmp/config/oss/RestTemplateConfig.java create mode 100644 src/main/java/kr/co/mcmp/config/oss/RestTemplateProvider.java create mode 100644 src/main/java/kr/co/mcmp/dto/oss/NexusFormatType.java create mode 100644 src/main/java/kr/co/mcmp/dto/oss/NexusRepositoryDto.java create mode 100644 src/main/java/kr/co/mcmp/exception/NexusClientException.java create mode 100644 src/main/java/kr/co/mcmp/oss/controller/OssController.java create mode 100644 src/main/java/kr/co/mcmp/oss/controller/OssTypeController.java create mode 100644 src/main/java/kr/co/mcmp/oss/dto/OssDto.java create mode 100644 src/main/java/kr/co/mcmp/oss/dto/OssTypeDto.java create mode 100644 src/main/java/kr/co/mcmp/oss/entity/Oss.java create mode 100644 src/main/java/kr/co/mcmp/oss/entity/OssType.java create mode 100644 src/main/java/kr/co/mcmp/oss/nexus/api/NexusRestClient.java create mode 100644 src/main/java/kr/co/mcmp/oss/nexus/api/NexusStatusApi.java create mode 100644 src/main/java/kr/co/mcmp/oss/nexus/exception/NexusException.java create mode 100644 src/main/java/kr/co/mcmp/oss/nexus/model/NexusComponent.java create mode 100644 src/main/java/kr/co/mcmp/oss/nexus/model/NexusPageComponent.java create mode 100644 src/main/java/kr/co/mcmp/oss/nexus/model/NexusRepository.java create mode 100644 src/main/java/kr/co/mcmp/oss/nexus/service/NexusService.java create mode 100644 src/main/java/kr/co/mcmp/oss/repository/OssRepository.java create mode 100644 src/main/java/kr/co/mcmp/oss/repository/OssTypeRepository.java create mode 100644 src/main/java/kr/co/mcmp/oss/service/OssService.java create mode 100644 src/main/java/kr/co/mcmp/oss/service/OssServiceImpl.java create mode 100644 src/main/java/kr/co/mcmp/oss/service/OssTypeService.java create mode 100644 src/main/java/kr/co/mcmp/oss/service/OssTypeServiceImpl.java create mode 100644 src/main/java/kr/co/mcmp/response/ResponseCode.java create mode 100644 src/main/java/kr/co/mcmp/response/ResponseWrapper.java create mode 100644 src/main/java/kr/co/mcmp/service/oss/NexusAdapterFactory.java create mode 100644 src/main/java/kr/co/mcmp/service/oss/NexusAdapterService.java create mode 100644 src/main/java/kr/co/mcmp/service/oss/NexusDockerAdapterService.java create mode 100644 src/main/java/kr/co/mcmp/service/oss/NexusHelmAdapterService.java create mode 100644 src/main/java/kr/co/mcmp/service/oss/NexusRawAdapterService.java create mode 100644 src/main/java/kr/co/mcmp/service/oss/NexusRepositoryAdapterClient.java create mode 100644 src/main/java/kr/co/mcmp/service/oss/NexusRepositoryService.java diff --git a/src/main/java/kr/co/mcmp/config/oss/RestTemplateConfig.java b/src/main/java/kr/co/mcmp/config/oss/RestTemplateConfig.java new file mode 100644 index 0000000..35acace --- /dev/null +++ b/src/main/java/kr/co/mcmp/config/oss/RestTemplateConfig.java @@ -0,0 +1,18 @@ +package kr.co.mcmp.config.oss; + +import org.apache.http.impl.client.HttpClientBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean(name = "customRestTemplate") + public RestTemplate restTemplate() { + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); + factory.setHttpClient(HttpClientBuilder.create().setMaxConnTotal(50).setMaxConnPerRoute(50).build()); + return new RestTemplate(factory); + } +} diff --git a/src/main/java/kr/co/mcmp/config/oss/RestTemplateProvider.java b/src/main/java/kr/co/mcmp/config/oss/RestTemplateProvider.java new file mode 100644 index 0000000..664ed35 --- /dev/null +++ b/src/main/java/kr/co/mcmp/config/oss/RestTemplateProvider.java @@ -0,0 +1,22 @@ +package kr.co.mcmp.config.oss; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +public class RestTemplateProvider implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + public static RestTemplate get() { + return applicationContext.getBean("customRestTemplate", RestTemplate.class); + } +} diff --git a/src/main/java/kr/co/mcmp/dto/oss/NexusFormatType.java b/src/main/java/kr/co/mcmp/dto/oss/NexusFormatType.java new file mode 100644 index 0000000..0039776 --- /dev/null +++ b/src/main/java/kr/co/mcmp/dto/oss/NexusFormatType.java @@ -0,0 +1,20 @@ +package kr.co.mcmp.dto.oss; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class NexusFormatType { + + @Schema(title = "레포지토리 포맷 유형") + private String format; + + @Schema(title = "레포지토리 타입 유형") + private String type; +} diff --git a/src/main/java/kr/co/mcmp/dto/oss/NexusRepositoryDto.java b/src/main/java/kr/co/mcmp/dto/oss/NexusRepositoryDto.java new file mode 100644 index 0000000..aa28b7a --- /dev/null +++ b/src/main/java/kr/co/mcmp/dto/oss/NexusRepositoryDto.java @@ -0,0 +1,193 @@ +package kr.co.mcmp.dto.oss; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Getter +public class NexusRepositoryDto { + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ResGetRepositoryDto { + @Schema(title = "레포지토리 이름", required = true) + @NotBlank + private String name; + + @Schema(title = "레포지토리 포맷 유형", required = true) + @NotBlank + private String format; + + @Schema(title = "레포지토리 타입 유형", required = true) + @NotBlank + private String type; + + @Schema(title = "레포지토리 접근 url", required = true) + @NotBlank + private String url; + + @Schema(title = "레포지토리 사용자 접근 가능 여부", required = true) + @NotNull + private Boolean online; + + @Valid + private ResGetStorageDto storage; + + @Valid + private ResGetDockerDto docker; + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ResGetStorageDto { + @Schema(title = "아티팩트를 저장하는 물리적 저장소 이름", required = true, example = "default") + @NotBlank + private String blobStoreName; + + @Schema(title = "저장되는 아티팩트 유형 일치 여부 검증", required = true) + @NotNull + private Boolean strictContentTypeValidation; + + @Schema(title = "레포지토리 읽기/쓰기 설정", required = true, example = "allow, allow_once, read_only") + @NotBlank + private String writePolicy; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ResGetDockerDto { + @Schema(title = "도커 registry 버전 지원(false: v2 지원)", required = true) + @NotNull + private Boolean v1Enabled; + + @Schema(title = "도커 클라이언트가 레포지토리에 접근할 때 기본 인증 사용 여부", required = true) + @NotNull + private Boolean forceBasicAuth; + + @Schema(title = "도커 레포지토리에 접근할 때 사용할 http 포트") + private Integer httpPort; + + @Schema(title = "도커 레포지토리에 접근할 때 사용할 https 포트") + private Integer httpsPort; + + @Schema(title = "도커 레포지토리에 접근할 때 사용할 서브도메인") + private String subdomain; + } + } + + @Getter + public static class ReqCreateRepositoryDto { + @Schema(title = "레포지토리 이름", required = true) + @NotBlank + private String name; + + @Schema(title = "레포지토리 사용자 접근 가능 여부", required = true) + @NotNull + private Boolean online; + + @Valid + private ReqCreateStorageDto storage; + + @Valid + private ReqCreateDockerDto docker; + + @Getter + public static class ReqCreateStorageDto { + @Schema(title = "아티팩트를 저장하는 물리적 저장소 이름", required = true, example = "default") + @NotBlank + private String blobStoreName; + + @Schema(title = "저장되는 아티팩트 유형 일치 여부 검증", required = true) + @NotNull + private Boolean strictContentTypeValidation; + + @Schema(title = "레포지토리 읽기/쓰기 설정", required = true, example = "allow, allow_once, read_only") + @NotBlank + private String writePolicy; + } + + @Getter + public static class ReqCreateDockerDto { + @Schema(title = "도커 registry 버전 지원(false: v2 지원)", required = true) + @NotNull + private Boolean v1Enabled; + + @Schema(title = "도커 클라이언트가 레포지토리에 접근할 때 기본 인증 사용 여부", required = true) + @NotNull + private Boolean forceBasicAuth; + + @Schema(title = "도커 레포지토리에 접근할 때 사용할 http 포트") + private Integer httpPort; + + @Schema(title = "도커 레포지토리에 접근할 때 사용할 https 포트") + private Integer httpsPort; + + @Schema(title = "도커 레포지토리에 접근할 때 사용할 서브도메인") + private String subdomain; + } + } + + @Getter + public static class ReqUpdateRepositoryDto { + @Schema(title = "레포지토리 이름", required = true) + @JsonIgnore + private String name; + + @Schema(title = "레포지토리 사용자 접근 가능 여부", required = true) + @NotNull + private Boolean online; + + @Valid + private ReqUpdateStorageDto storage; + + @Valid + private ReqUpdateDockerDto docker; + + @Getter + public static class ReqUpdateStorageDto { + @Schema(title = "아티팩트를 저장하는 물리적 저장소 이름", required = true, example = "default") + @JsonIgnore + private String blobStoreName; + + @Schema(title = "저장되는 아티팩트 유형 일치 여부 검증", required = true) + @NotNull + private Boolean strictContentTypeValidation; + + @Schema(title = "레포지토리 읽기/쓰기 설정", required = true, example = "allow, allow_once, read_only") + @NotBlank + private String writePolicy; + } + + @Getter + public static class ReqUpdateDockerDto { + @Schema(title = "도커 registry 버전 지원(false: v2 지원)", required = true) + @NotNull + private Boolean v1Enabled; + + @Schema(title = "도커 클라이언트가 레포지토리에 접근할 때 기본 인증 사용 여부", required = true) + @NotNull + private Boolean forceBasicAuth; + + @Schema(title = "도커 레포지토리에 접근할 때 사용할 http 포트") + private Integer httpPort; + + @Schema(title = "도커 레포지토리에 접근할 때 사용할 https 포트") + private Integer httpsPort; + + @Schema(title = "도커 레포지토리에 접근할 때 사용할 서브도메인") + private String subdomain; + } + } +} diff --git a/src/main/java/kr/co/mcmp/exception/NexusClientException.java b/src/main/java/kr/co/mcmp/exception/NexusClientException.java new file mode 100644 index 0000000..941119e --- /dev/null +++ b/src/main/java/kr/co/mcmp/exception/NexusClientException.java @@ -0,0 +1,35 @@ +package kr.co.mcmp.exception; + +import kr.co.mcmp.response.ResponseCode; +import lombok.Getter; + +@Getter +public class NexusClientException extends RuntimeException { + + private static final long serialVersionUID = -7883846160384968138L; + + ResponseCode responseCode = ResponseCode.NEXUS_CLIENT_ERROR; + private String detail; + + public NexusClientException() { + this(ResponseCode.NEXUS_CLIENT_ERROR); + } + + public NexusClientException(ResponseCode responseCode) { + this.responseCode = responseCode; + } + + public NexusClientException(ResponseCode responseCode, String detail) { + this.responseCode = responseCode; + this.detail = detail; + } + + public NexusClientException(String detail) { + this.detail = detail; + } + + public NexusClientException(Throwable cause) { + super(cause); + this.detail = cause.getMessage(); + } +} diff --git a/src/main/java/kr/co/mcmp/oss/controller/OssController.java b/src/main/java/kr/co/mcmp/oss/controller/OssController.java new file mode 100644 index 0000000..079ce9c --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/controller/OssController.java @@ -0,0 +1,85 @@ +package kr.co.mcmp.oss.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.co.mcmp.oss.dto.OssDto; +import kr.co.mcmp.oss.service.OssService; +import kr.co.mcmp.response.ResponseCode; +import kr.co.mcmp.response.ResponseWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "oss", description = "oss 관리") +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/oss") +@RestController +public class OssController { + + private final OssService ossService; + + @Operation(summary = "OSS 목록 조회", description = "oss 모든 목록조회" ) + @GetMapping("/list") + public ResponseWrapper> getOssList() { + return new ResponseWrapper<>(ossService.getAllOssList()); + } + + @Operation(summary = "OSS 목록 조회", description = "oss 목록조회(Keyword)" ) + @GetMapping("/list/{ossTypeName}") + public ResponseWrapper> getOssList(@PathVariable("ossTypeName") String ossTypeName) { + return new ResponseWrapper<>(ossService.getOssList(ossTypeName)); + } + + @Operation(summary = "OSS 등록", description = "oss 등록") + @PostMapping + public ResponseWrapper registOss(@RequestBody OssDto ossDto) { + return new ResponseWrapper<>(ossService.registOss(ossDto)); + } + + @Operation(summary = "OSS 수정", description = "oss 수정") + @PatchMapping("/{ossIdx}") + public ResponseWrapper updateOss(@PathVariable Long ossIdx, @RequestBody OssDto ossDto) { + if ( ossIdx != 0 || ossDto.getOssIdx() != 0 ) { + return new ResponseWrapper<>(ossService.updateOss(ossDto)); + } + return new ResponseWrapper<>(ResponseCode.BAD_REQUEST, "OssIdx"); + } + + @Operation(summary = "OSS 삭제", description = "oss 삭제") + @DeleteMapping("/{ossIdx}") + public ResponseWrapper deleteOss(@PathVariable Long ossIdx) { + return new ResponseWrapper<>(ossService.deleteOss(ossIdx)); + } + + @Operation(summary = "OSS 상세 조회", description = "oss 상세조회" ) + @GetMapping("/{ossIdx}") + public ResponseWrapper detailOss(@PathVariable Long ossIdx) { + return new ResponseWrapper<>(ossService.detailOss(ossIdx)); + } + + @Operation(summary = "OSS 정보 중복 체크(oss명, url, username)", description = "true : 중복 / false : 중복 아님") + @GetMapping("/duplicate") + public ResponseWrapper isOssInfoDuplicated(@RequestParam String ossName, @RequestParam String ossUrl, @RequestParam String ossUsername) { + if ( StringUtils.isBlank(ossName) ) { + return new ResponseWrapper<>(ResponseCode.BAD_REQUEST, "ossName"); + } + else if ( StringUtils.isBlank(ossUrl) ) { + return new ResponseWrapper<>(ResponseCode.BAD_REQUEST, "ossUrl"); + } + else if ( StringUtils.isBlank(ossUsername) ) { + return new ResponseWrapper<>(ResponseCode.BAD_REQUEST, "ossUsername"); + } + OssDto ossDto = OssDto.setOssAttributesDuplicate(ossName, ossUrl, ossUsername); + return new ResponseWrapper<>(ossService.isOssInfoDuplicated(ossDto)); + } + + @Operation(summary = "OSS 연결확인", description = "oss 연결 확인") + @PostMapping("/connection-check") + public ResponseWrapper checkConnection(@RequestBody OssDto ossDto) { + return new ResponseWrapper<>(ossService.checkConnection(ossDto)); + } +} diff --git a/src/main/java/kr/co/mcmp/oss/controller/OssTypeController.java b/src/main/java/kr/co/mcmp/oss/controller/OssTypeController.java new file mode 100644 index 0000000..6a3bd3b --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/controller/OssTypeController.java @@ -0,0 +1,56 @@ +package kr.co.mcmp.oss.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.co.mcmp.oss.dto.OssTypeDto; +import kr.co.mcmp.oss.service.OssTypeService; +import kr.co.mcmp.response.ResponseCode; +import kr.co.mcmp.response.ResponseWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "oss 타입", description = "JENKINS / GITLAB / TUMBLEBUG / Etc...") +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/ossType") +@RestController +public class OssTypeController { + + private final OssTypeService ossTypeService; + + @Operation(summary = "OSS 타입 목록 조회", description = "oss Type 목록조회" ) + @GetMapping("/list") + public ResponseWrapper> getOssTypeList() { + return new ResponseWrapper<>(ossTypeService.getAllOssTypeList()); + } + + @Operation(summary = "OSS 타입 등록", description = "oss Type 등록") + @PostMapping + public ResponseWrapper registOssType(@RequestBody OssTypeDto ossTypeDto) { + return new ResponseWrapper<>(ossTypeService.registOssType(ossTypeDto)); + } + + @Operation(summary = "OSS 타입 수정", description = "oss Type 수정") + @PatchMapping("/{ossTypeIdx}") + public ResponseWrapper updateOssType(@PathVariable Long ossTypeIdx, @RequestBody OssTypeDto ossTypeDto) { + if ( ossTypeIdx != 0 || ossTypeDto.getOssTypeIdx() != 0 ) { + return new ResponseWrapper<>(ossTypeService.updateOssType(ossTypeDto)); + } + return new ResponseWrapper<>(ResponseCode.BAD_REQUEST, "OssTypeIdx"); + } + + @Operation(summary = "OSS 타입 삭제", description = "oss Type 삭제") + @DeleteMapping("/{ossTypeIdx}") + public ResponseWrapper deleteOssType(@PathVariable Long ossTypeIdx) { + return new ResponseWrapper<>(ossTypeService.deleteOssType(ossTypeIdx)); + } + + @Operation(summary = "OSS 타입 상세", description = "oss Type 상세정보") + @GetMapping("/{ossTypeIdx}") + public ResponseWrapper detailOssType(@PathVariable Long ossTypeIdx) { + return new ResponseWrapper<>(ossTypeService.detailOssType(ossTypeIdx)); + } +} diff --git a/src/main/java/kr/co/mcmp/oss/dto/OssDto.java b/src/main/java/kr/co/mcmp/oss/dto/OssDto.java new file mode 100644 index 0000000..ed13e3a --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/dto/OssDto.java @@ -0,0 +1,90 @@ +package kr.co.mcmp.oss.dto; + +import kr.co.mcmp.oss.entity.Oss; +import lombok.Builder; +import lombok.Getter; + + +@Getter +@Builder +public class OssDto { + + private Long ossIdx; + private Long ossTypeIdx; + private String ossName; + private String ossDesc; + private String ossUrl; + private String ossUsername; + private String ossPassword; + + // from : 외부 (entity -> dto) + public static OssDto from(Oss oss) { + return OssDto.builder() + .ossIdx(oss.getOssIdx()) + .ossTypeIdx(oss.getOssType().getOssTypeIdx()) + .ossName(oss.getOssName()) + .ossDesc(oss.getOssDesc()) + .ossUrl(oss.getOssUrl()) + .ossUsername(oss.getOssUsername()) + .ossPassword(oss.getOssPassword()) + .build(); + } + + // of : 내부 (dto -> dto) + public static OssDto of(OssDto ossDto) { + return OssDto.builder() + .ossIdx(ossDto.getOssIdx()) + .ossTypeIdx(ossDto.getOssTypeIdx()) + .ossName(ossDto.getOssName()) + .ossDesc(ossDto.getOssDesc()) + .ossUrl(ossDto.getOssUrl()) + .ossUsername(ossDto.getOssUsername()) + .ossPassword(ossDto.getOssPassword()) + .build(); + } + + // toEntity : Entity 변환 (dto -> entity) + public static Oss toEntity(OssDto ossDto, OssTypeDto ossTypeDto) { + return Oss.builder() + .ossIdx(ossDto.getOssIdx()) + .ossType(OssTypeDto.toEntity(ossTypeDto)) + .ossName(ossDto.getOssName()) + .ossDesc(ossDto.getOssDesc()) + .ossUrl(ossDto.getOssUrl()) + .ossUsername(ossDto.getOssUsername()) + .ossPassword(ossDto.getOssPassword()) + .build(); + } + + // 패스워드 Encript set + public static OssDto withModifiedEncriptPassword(OssDto ossDto, String password) { + return OssDto.builder() + .ossIdx(ossDto.getOssIdx()) + .ossTypeIdx(ossDto.getOssTypeIdx()) + .ossName(ossDto.getOssName()) + .ossDesc(ossDto.getOssDesc()) + .ossUrl(ossDto.getOssUrl()) + .ossUsername(ossDto.getOssUsername()) + .ossPassword(password) + .build(); + } + // 패스워드 decrypt set + public static OssDto withDetailDecryptPassword(Oss oss, String password) { + return OssDto.builder() + .ossIdx(oss.getOssIdx()) + .ossTypeIdx(oss.getOssType().getOssTypeIdx()) + .ossName(oss.getOssName()) + .ossDesc(oss.getOssDesc()) + .ossUrl(oss.getOssUrl()) + .ossUsername(oss.getOssUsername()) + .ossPassword(password) + .build(); + } // Duplicate Object set + public static OssDto setOssAttributesDuplicate(String ossName, String ossUrl, String ossUsername) { + return OssDto.builder() + .ossName(ossName) + .ossUrl(ossUrl) + .ossUsername(ossUsername) + .build(); + } +} diff --git a/src/main/java/kr/co/mcmp/oss/dto/OssTypeDto.java b/src/main/java/kr/co/mcmp/oss/dto/OssTypeDto.java new file mode 100644 index 0000000..a026995 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/dto/OssTypeDto.java @@ -0,0 +1,42 @@ +package kr.co.mcmp.oss.dto; + +import kr.co.mcmp.oss.entity.OssType; +import lombok.Builder; +import lombok.Getter; + + +@Getter +@Builder +public class OssTypeDto { + + private Long ossTypeIdx; + private String ossTypeName; + private String ossTypeDesc; + + // from : 외부 (entity -> dto) + public static OssTypeDto from(OssType ossType) { + return OssTypeDto.builder() + .ossTypeIdx(ossType.getOssTypeIdx()) + .ossTypeName(ossType.getOssTypeName()) + .ossTypeDesc(ossType.getOssTypeDesc()) + .build(); + } + + // of : 내부 (dto -> dto) + public static OssTypeDto of(OssTypeDto ossTypeDto) { + return OssTypeDto.builder() + .ossTypeIdx(ossTypeDto.getOssTypeIdx()) + .ossTypeName(ossTypeDto.getOssTypeName()) + .ossTypeDesc(ossTypeDto.getOssTypeDesc()) + .build(); + } + + // toEntity : Entity 변환 (dto -> entity) + public static OssType toEntity(OssTypeDto ossTypeDto) { + return OssType.builder() + .ossTypeIdx(ossTypeDto.getOssTypeIdx()) + .ossTypeName(ossTypeDto.getOssTypeName()) + .ossTypeDesc(ossTypeDto.getOssTypeDesc()) + .build(); + } +} diff --git a/src/main/java/kr/co/mcmp/oss/entity/Oss.java b/src/main/java/kr/co/mcmp/oss/entity/Oss.java new file mode 100644 index 0000000..e3d76fd --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/entity/Oss.java @@ -0,0 +1,38 @@ +package kr.co.mcmp.oss.entity; + +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "oss") +public class Oss { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "oss_idx") + private Long ossIdx; + + @ManyToOne + @JoinColumn(name = "oss_type_idx", nullable = false) + private OssType ossType; + + @Column(name = "oss_name", nullable = false) + private String ossName; + + @Column(name = "oss_desc") + private String ossDesc; + + @Column(name = "oss_url", nullable = false) + private String ossUrl; + + @Column(name = "oss_username") + private String ossUsername; + + @Column(name = "oss_password") + private String ossPassword; + +} \ No newline at end of file diff --git a/src/main/java/kr/co/mcmp/oss/entity/OssType.java b/src/main/java/kr/co/mcmp/oss/entity/OssType.java new file mode 100644 index 0000000..d9a36d3 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/entity/OssType.java @@ -0,0 +1,24 @@ +package kr.co.mcmp.oss.entity; + +import lombok.*; + +import javax.persistence.*; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "oss_type") +public class OssType { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "oss_type_idx") + private Long ossTypeIdx; + + @Column(name = "oss_type_name", nullable = false) + private String ossTypeName; + + @Column(name = "oss_type_desc") + private String ossTypeDesc; +} \ No newline at end of file diff --git a/src/main/java/kr/co/mcmp/oss/nexus/api/NexusRestClient.java b/src/main/java/kr/co/mcmp/oss/nexus/api/NexusRestClient.java new file mode 100644 index 0000000..30cddc0 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/nexus/api/NexusRestClient.java @@ -0,0 +1,217 @@ +package kr.co.mcmp.oss.nexus.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.mcmp.oss.dto.OssDto; +import kr.co.mcmp.oss.nexus.exception.NexusException; +import kr.co.mcmp.util.AES256Util; +import kr.co.mcmp.util.Base64Util; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.http.*; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.net.ssl.SSLContext; +import java.lang.reflect.Type; +import java.net.SocketTimeoutException; +import java.nio.charset.Charset; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; + +@Slf4j +@Component +public class NexusRestClient { + + private static String NEXUS_API_BASE_URL = "/service/rest/"; + + private static final int CONNECT_TIMEOUT = 10000; + private static final int READ_TIMEOUT = 10000; + + public UriComponentsBuilder getUriBuilder(String url, String path) { + return UriComponentsBuilder.fromHttpUrl(url).pathSegment(NEXUS_API_BASE_URL, path); + } + + public HttpHeaders getHeaderByPassword(OssDto nexus) throws Exception { + if ( StringUtils.isBlank(nexus.getOssUsername()) ) { + throw new NexusException(HttpStatus.BAD_REQUEST, "nexus username is not found"); + } + if ( StringUtils.isBlank(nexus.getOssPassword()) ) { + throw new NexusException(HttpStatus.BAD_REQUEST, "nexus user password is not found"); + } + + String plainTextPassword = AES256Util.decrypt(nexus.getOssPassword()); + + String value = nexus.getOssUsername() + ":" + plainTextPassword; + String encodedValue = Base64Util.base64Encoding(value); + log.info("basicAuth >>> Basic {}", encodedValue); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8"))); + headers.set("Authorization", "Basic "+encodedValue); + + return headers; + } + + /** + * SSL 인증서 무시 + * @return + * @throws KeyManagementException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + */ + private RestTemplate getSkipSslCertificateVerficationRestTemplate() { + TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true; + SSLContext sslContext = null; + try { + sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); + } catch (KeyManagementException e) { + log.error(e.getMessage(), e); + throw new NexusException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); + } catch (NoSuchAlgorithmException e) { + log.error(e.getMessage(), e); + throw new NexusException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); + } catch (KeyStoreException e) { + log.error(e.getMessage(), e); + throw new NexusException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); + } + + CloseableHttpClient httpClient = HttpClients.custom() + .setSSLContext(sslContext) + .setSSLHostnameVerifier(new NoopHostnameVerifier()) + .disableRedirectHandling() + .build(); + + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); + requestFactory.setConnectTimeout(CONNECT_TIMEOUT); + requestFactory.setReadTimeout(READ_TIMEOUT); + requestFactory.setHttpClient(httpClient); + + RestTemplate restTemplate = new RestTemplate(requestFactory); + return restTemplate; + } + + private HttpEntity getHttpEntity(U body, HttpHeaders headers, String uri){ + ObjectMapper objectMapper = new ObjectMapper(); + String requestBody = null; + if(body != null){ + try { + requestBody = objectMapper.writeValueAsString(body); + } catch (JsonProcessingException e) { + log.error("request body JsonProcessingException", e); + throw new NexusException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Request body JsonProcessingException", uri); + } + } + HttpEntity entity = new HttpEntity((U) requestBody, headers); + return entity; + } + + public Object request(OssDto nexus, String apiUri, HttpMethod httpMethod, U body, Class clazz) { + T responseBody = null; + + try { + HttpHeaders headers = getHeaderByPassword(nexus); + + UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(apiUri).build(); + HttpEntity entity = new HttpEntity<>(body, headers); + + RestTemplate restTemplate = this.getSkipSslCertificateVerficationRestTemplate(); + ResponseEntity response = restTemplate.exchange(uriComponents.toString(), httpMethod, entity, clazz); + if ( response.getBody() != null ) { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + responseBody = objectMapper.convertValue(response.getBody(), new TypeReference(){ + @Override + public Type getType() { + return clazz; + } + }); + } + } catch ( HttpClientErrorException e) { + log.error("[requestNexusAPI] ## Message = " + e.getMessage(), e); + log.error("[requestNexusAPI] ## Response Code = " + e.getStatusCode().value()); + log.error("[requestNexusAPI] ## Response Body = " + e.getResponseBodyAsString()); + throw new NexusException(e.getStatusCode().value(), e.getMessage(), apiUri); + } catch ( RestClientException e ) { + log.error("요청하신 URL("+apiUri+")은 유효하지 않습니다."); + if ( e.getRootCause() instanceof SocketTimeoutException) { + log.error("SocketTimeoutException", e); + throw new NexusException(HttpStatus.REQUEST_TIMEOUT.value(), "The URL you requested is not valid.", apiUri); + } else if ( e.getRootCause() instanceof ConnectTimeoutException) { + log.error("ConnectTimeoutException", e); + throw new NexusException(HttpStatus.REQUEST_TIMEOUT.value(), "The URL you requested is not valid.", apiUri); + } else { + log.error(e.getMessage(), e); + throw new NexusException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage(), apiUri); + } + } catch (Exception ex) { + log.error("요청하신 URL("+apiUri+") 처리 중 에러가 발생했습니다."); + log.error(ex.getMessage(), ex); + throw new NexusException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An error occurred while processing the requested URL.", apiUri); + } + + return responseBody; + } + + /** + * Nexus URL 연결 체크(200은 인스턴스가 읽기 요청을 처리할 수 있음을 나타내고 503은 그렇지 않은 경우) + * @param apiUri + * @param httpMethod + * @return + */ + public HttpStatus checkNexusConnection(String apiUri, HttpMethod httpMethod) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8"))); + + UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(apiUri).build(); + HttpEntity entity = this.getHttpEntity(null, headers, apiUri); + + RestTemplate restTemplate = this.getSkipSslCertificateVerficationRestTemplate(); + ResponseEntity response = null; + try { + response = restTemplate.exchange(uriComponents.toString(), httpMethod, entity, Object.class); + if ( response != null ) { + return response.getStatusCode(); + } + else { + return null; + } + } catch ( HttpClientErrorException e) { + log.error("[checkNexusConnection] ## Message = " + e.getMessage(), e); + log.error("[checkNexusConnection] ## Response Code = " + e.getStatusCode().value()); + log.error("[checkNexusConnection] ## Response Body = " + e.getResponseBodyAsString()); + throw new NexusException(e.getStatusCode().value(), e.getMessage(), apiUri); + } catch ( RestClientException e ) { + log.error("요청하신 URL("+apiUri+")은 유효하지 않습니다."); + if ( e.getRootCause() instanceof SocketTimeoutException) { + log.error("SocketTimeoutException", e); + throw new NexusException(HttpStatus.REQUEST_TIMEOUT.value(), "The URL you requested is not valid.", apiUri); + } else if ( e.getRootCause() instanceof ConnectTimeoutException) { + log.error("ConnectTimeoutException", e); + throw new NexusException(HttpStatus.REQUEST_TIMEOUT.value(), "The URL you requested is not valid.", apiUri); + } else { + log.error(e.getMessage(), e); + throw new NexusException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage(), apiUri); + } + } catch (Exception ex) { + log.error("요청하신 URL("+apiUri+") 처리 중 에러가 발생했습니다."); + log.error(ex.getMessage(), ex); + throw new NexusException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An error occurred while processing the requested URL.", apiUri); + } + } +} diff --git a/src/main/java/kr/co/mcmp/oss/nexus/api/NexusStatusApi.java b/src/main/java/kr/co/mcmp/oss/nexus/api/NexusStatusApi.java new file mode 100644 index 0000000..56463ec --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/nexus/api/NexusStatusApi.java @@ -0,0 +1,25 @@ +package kr.co.mcmp.oss.nexus.api; + +import kr.co.mcmp.oss.dto.OssDto; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class NexusStatusApi { + + @Autowired + private NexusRestClient client; + + public HttpStatus statusEndpoint(OssDto nexus) { + final String path = "/v1/status"; + String url = client.getUriBuilder(nexus.getOssUrl(), path).build().toUriString(); + log.debug("endpoint url : {}", url); + + return client.checkNexusConnection(url, HttpMethod.GET); + } + +} diff --git a/src/main/java/kr/co/mcmp/oss/nexus/exception/NexusException.java b/src/main/java/kr/co/mcmp/oss/nexus/exception/NexusException.java new file mode 100644 index 0000000..b78b388 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/nexus/exception/NexusException.java @@ -0,0 +1,32 @@ +package kr.co.mcmp.oss.nexus.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class NexusException extends RuntimeException { + + private static final long serialVersionUID = -3088444536163499090L; + private int code; + private String messag; + private String detail; + + public NexusException(int code, String message) { + this.code = code; + this.messag = message; + } + + public NexusException(int code, String message, String detail) { + this(code, message); + this.detail = detail; + } + + public NexusException(HttpStatus httpStatus) { + this(httpStatus.value(), httpStatus.getReasonPhrase()); + } + + public NexusException(HttpStatus httpStatus, String detail) { + this(httpStatus.value(), httpStatus.getReasonPhrase(), detail); + } + +} diff --git a/src/main/java/kr/co/mcmp/oss/nexus/model/NexusComponent.java b/src/main/java/kr/co/mcmp/oss/nexus/model/NexusComponent.java new file mode 100644 index 0000000..ac682c3 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/nexus/model/NexusComponent.java @@ -0,0 +1,23 @@ +package kr.co.mcmp.oss.nexus.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +@Getter +@Setter +@Tag(name = "NexusComponent", description = "Nexus Components") +@JsonIgnoreProperties(ignoreUnknown=true) +public class NexusComponent implements Serializable { + + private static final long serialVersionUID = 6695416538557612577L; + + private String id; + private String repository; + private String format; + private String name; + private String version; +} diff --git a/src/main/java/kr/co/mcmp/oss/nexus/model/NexusPageComponent.java b/src/main/java/kr/co/mcmp/oss/nexus/model/NexusPageComponent.java new file mode 100644 index 0000000..ae8d837 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/nexus/model/NexusPageComponent.java @@ -0,0 +1,19 @@ +package kr.co.mcmp.oss.nexus.model; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.List; + +@Getter +@Setter +@Tag(name = "NexusPageComponent", description = "넥서스 컴포넌트 페이지 객체") +public class NexusPageComponent implements Serializable { + + private static final long serialVersionUID = 6346599677940737012L; + + private List items; + private String continuationToken; +} diff --git a/src/main/java/kr/co/mcmp/oss/nexus/model/NexusRepository.java b/src/main/java/kr/co/mcmp/oss/nexus/model/NexusRepository.java new file mode 100644 index 0000000..d30cdf8 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/nexus/model/NexusRepository.java @@ -0,0 +1,22 @@ +package kr.co.mcmp.oss.nexus.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +@Getter +@Setter +@Tag(name = "NexusRepository", description = "넥서스 Repository 객체") +@JsonIgnoreProperties +public class NexusRepository implements Serializable { + + private static final long serialVersionUID = 972782900597914206L; + + private String id; + private String format; + private String type; + private String url; +} diff --git a/src/main/java/kr/co/mcmp/oss/nexus/service/NexusService.java b/src/main/java/kr/co/mcmp/oss/nexus/service/NexusService.java new file mode 100644 index 0000000..796261a --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/nexus/service/NexusService.java @@ -0,0 +1,38 @@ +package kr.co.mcmp.oss.nexus.service; + +import kr.co.mcmp.oss.dto.OssDto; +import kr.co.mcmp.oss.nexus.api.NexusStatusApi; +import kr.co.mcmp.oss.nexus.exception.NexusException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class NexusService { + + @Autowired + private NexusStatusApi statusApi; + + /** + * 넥서스 URL 연결 체크 + * @param nexus + * @return + */ + public boolean checkNexusConnection(OssDto nexus) { + boolean checked = false; + try { + HttpStatus httpStatus = statusApi.statusEndpoint(nexus); + if ( httpStatus == HttpStatus.OK ) { + checked = true; + } + } catch (NexusException e) { + log.error("[getNexusRepositoryUrl] nexus error : {}", e.getMessage()); + } catch (Exception e) { + log.error("[getNexusRepositoryUrl] error : {}", e.getMessage()); + } + + return checked; + } +} diff --git a/src/main/java/kr/co/mcmp/oss/repository/OssRepository.java b/src/main/java/kr/co/mcmp/oss/repository/OssRepository.java new file mode 100644 index 0000000..d3b07f2 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/repository/OssRepository.java @@ -0,0 +1,22 @@ +package kr.co.mcmp.oss.repository; + +import kr.co.mcmp.oss.entity.Oss; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface OssRepository extends JpaRepository { + List findAll(); + List findByOssName(String ossName); + @Query("SELECT o FROM Oss o WHERE o.ossType.ossTypeIdx IN :ossTypeIdxs") + List findByOssTypeIdxIn(@Param("ossTypeIdxs") List ossTypeIdxs); + Boolean existsByOssNameAndOssUrlAndOssUsername(String ossName, String ossUrl, String ossUsername); + Oss save(Oss oss); + Oss findByOssType_OssTypeName(String ossTypeName); + Oss findByOssIdx(Long ossIdx); + void deleteByOssIdx(Long ossIdx); +} diff --git a/src/main/java/kr/co/mcmp/oss/repository/OssTypeRepository.java b/src/main/java/kr/co/mcmp/oss/repository/OssTypeRepository.java new file mode 100644 index 0000000..57e1d86 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/repository/OssTypeRepository.java @@ -0,0 +1,16 @@ +package kr.co.mcmp.oss.repository; + +import kr.co.mcmp.oss.dto.OssTypeDto; +import kr.co.mcmp.oss.entity.OssType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface OssTypeRepository extends JpaRepository { + List findByOssTypeName(String ossTypeName); + OssType findByOssTypeIdx(Long ossTypeIdx); + OssType save(OssTypeDto ossTypeDto); + void deleteById(Long ossTypeIdx); +} diff --git a/src/main/java/kr/co/mcmp/oss/service/OssService.java b/src/main/java/kr/co/mcmp/oss/service/OssService.java new file mode 100644 index 0000000..46d1b64 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/service/OssService.java @@ -0,0 +1,20 @@ +package kr.co.mcmp.oss.service; + +import kr.co.mcmp.oss.dto.OssDto; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public interface OssService { + List getAllOssList(); + List getOssList(String ossTypeName); + Boolean isOssInfoDuplicated(OssDto ossDto); + Long registOss(OssDto ossDto); + Long updateOss(OssDto ossDto); + @Transactional + Boolean deleteOss(Long ossIdx); + Boolean checkConnection(OssDto ossDto); + OssDto detailOss(Long ossIdx); +} \ No newline at end of file diff --git a/src/main/java/kr/co/mcmp/oss/service/OssServiceImpl.java b/src/main/java/kr/co/mcmp/oss/service/OssServiceImpl.java new file mode 100644 index 0000000..e443789 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/service/OssServiceImpl.java @@ -0,0 +1,256 @@ +package kr.co.mcmp.oss.service; + +import kr.co.mcmp.oss.dto.OssDto; +import kr.co.mcmp.oss.dto.OssTypeDto; +import kr.co.mcmp.oss.entity.Oss; +import kr.co.mcmp.oss.nexus.service.NexusService; +import kr.co.mcmp.oss.repository.OssRepository; +import kr.co.mcmp.oss.repository.OssTypeRepository; +import kr.co.mcmp.util.AES256Util; +import kr.co.mcmp.util.Base64Util; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.stream.Collectors; + +@Log4j2 +@RequiredArgsConstructor +@Service +public class OssServiceImpl implements OssService { + + private final OssRepository ossRepository; + + private final OssTypeRepository ossTypeRepository; + + private final NexusService nexusService; + + /** + * OSS 목록 조회 + * @return List ossDtoList + */ + @Override + public List getAllOssList() { + List ossList = ossRepository.findAll() + .stream() + .map(OssDto::from) + .collect(Collectors.toList()); + + if ( !CollectionUtils.isEmpty(ossList) ) { + ossList = ossList.stream() + .map(ossDto -> OssDto.withModifiedEncriptPassword(ossDto, encodingBase64String(decryptAesString(ossDto.getOssPassword())))) + .collect(Collectors.toList()); + } + + return ossList; + } + + /** + * OSS 목록 조회 + * @param ossTypeName + * @return List ossDtoList + */ + @Override + public List getOssList(String ossTypeName) { + List ossTypeList = ossTypeRepository.findByOssTypeName(ossTypeName) + .stream() + .map(OssTypeDto::from) + .collect(Collectors.toList()); + log.info(ossTypeList); + + // ossTypeList에서 ossTypeIdx 목록을 추출 + List ossTypeIdxList = ossTypeList.stream() + .map(OssTypeDto::getOssTypeIdx) + .collect(Collectors.toList()); + + List ossList = ossRepository.findByOssTypeIdxIn(ossTypeIdxList) + .stream() + .map(OssDto::from) + .collect(Collectors.toList()); + + if ( !CollectionUtils.isEmpty(ossList) ) { + ossList = ossList + .stream() + .map(ossDto -> OssDto.withModifiedEncriptPassword(ossDto, encodingBase64String(decryptAesString(ossDto.getOssPassword())))) + .collect(Collectors.toList()); + } + + return ossList; + } + + /** + * OSS 등록 + * @param ossDto + * @return + */ + @Transactional + @Override + public Long registOss(OssDto ossDto) { + OssTypeDto ossTypeDto = OssTypeDto.from(ossTypeRepository.findByOssTypeIdx(ossDto.getOssTypeIdx())); + ossDto = ossDto.withModifiedEncriptPassword(ossDto, encryptAesString(ossDto.getOssPassword())); + ossDto = OssDto.from(ossRepository.save(OssDto.toEntity(ossDto, ossTypeDto))); + return ossDto.getOssIdx(); + } + + /** + * OSS 수정 + * @param ossDto + * @return + */ + @Override + public Long updateOss(OssDto ossDto) { + OssTypeDto ossTypeDto = OssTypeDto.from(ossTypeRepository.findByOssTypeIdx(ossDto.getOssTypeIdx())); + + ossDto = ossDto.withModifiedEncriptPassword(ossDto, encryptAesString(ossDto.getOssPassword())); + ossRepository.save(OssDto.toEntity(ossDto, ossTypeDto)); + return ossDto.getOssIdx(); + } + + /** + * OSS 삭제 + * @param ossIdx + */ + @Transactional + @Override + public Boolean deleteOss(Long ossIdx) { + try { + OssDto deleteOss = OssDto.from(ossRepository.findByOssIdx(ossIdx)); + ossRepository.deleteByOssIdx(ossIdx); + return true; + } catch (EmptyResultDataAccessException e) { + return false; + } catch (Exception e) { + return false; + } + } + + /** + * OSS 연결 확인 + * @param ossDto + * TODO : 추후 OSS 추가 + */ + @Transactional + @Override + public Boolean checkConnection(OssDto ossDto) { + OssTypeDto osstypeDto = OssTypeDto.from(ossTypeRepository.findByOssTypeIdx(ossDto.getOssTypeIdx())); + + if(!osstypeDto.getOssTypeName().isEmpty()) { + switch(osstypeDto.getOssTypeName()) { + case "NEXUS" : + if (StringUtils.isBlank(ossDto.getOssUrl()) || + StringUtils.isBlank(ossDto.getOssUsername()) ) { + log.error("접속정보 누락"); + return false; + } + + // Front에서 Base64Encoding한 데이터를 복호화하여 AES256 암호화 함. + ossDto.withModifiedEncriptPassword(ossDto, encryptAesString(ossDto.getOssPassword())); + return nexusService.checkNexusConnection(ossDto); + + default: + log.debug("[checkConnection] oss code >>> {}", osstypeDto.getOssTypeName()); + log.error("Code is not registered] ossTypeName >>> {}", osstypeDto.getOssTypeName()); + return false; + } + } + else { + log.debug("[checkConnection] oss code >>> {}", osstypeDto.getOssTypeName()); + log.error("[OssTypeName is Null] ossTypeIdx >>> {}", ossDto.getOssTypeIdx()); + return false; + } + } + + /** + * OSS 정보 상세 조회 + * @param ossIdx + * @return + */ + public OssDto detailOss(Long ossIdx) { + Oss oss = ossRepository.findByOssIdx(ossIdx); + return OssDto.withDetailDecryptPassword(oss, encodingBase64String(decryptAesString(oss.getOssPassword()))); + } + +// /** +// * OSS 정보 상세 조회 +// * @param ossCd +// * @return +// */ +// public OssDto getOssByOssCd(String ossCd) { +// return OssDto.from(ossRepository.findByOssType_OssTypeName(ossCd)); +// } + + /** + * OSS 정보 중복 체크(ossName, ossUrl, ossUsername) + * @param ossDto + * 중복: true / 아니면 false + * @return + */ + public Boolean isOssInfoDuplicated(OssDto ossDto) { + return ossRepository.existsByOssNameAndOssUrlAndOssUsername( + ossDto.getOssName(), + ossDto.getOssUrl(), + ossDto.getOssUsername()); + } + + /** + * 패스워드 암호화 (Front로 Base64Encoding한 데이터를 보냄.) + * @param str + * @return + */ + public String encryptBase64String(String str) { + if ( StringUtils.isNotBlank(str) ) { + return Base64Util.base64Encoding(AES256Util.decryptOssPassword(str)); + } + else { + return null; + } + } + + /** + * 패스워드 암호화 (Front에서 Base64Encoding한 데이터를 복호화하여 AES256 암호화 함.) + * @param str + * @return + */ + public String encryptAesString(String str) { + if ( StringUtils.isNotBlank(str) ) { + return AES256Util.encryptOssPassword(Base64Util.base64Decoding(str)); + } + else { + return null; + } + } + /** + * 패스워드/토큰 암호화 (Front로 Base64Encoding한 데이터를 보내.) + * @param str + * @return + */ + public String encodingBase64String(String str) { + if ( StringUtils.isNotBlank(str) ) { + return Base64Util.base64Encoding(str); + } + else { + return null; + } + } + + /** + * 패스워드 복호화 + * @param encryptedStr + * @return + */ + public String decryptAesString(String encryptedStr) { + if (StringUtils.isNotBlank(encryptedStr)) { + // AES256으로 암호화된 문자열을 복호화 + String decrypted = AES256Util.decryptOssPassword(encryptedStr); + // 복호화된 문자열을 Base64로 인코딩 + return decrypted; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/mcmp/oss/service/OssTypeService.java b/src/main/java/kr/co/mcmp/oss/service/OssTypeService.java new file mode 100644 index 0000000..94b4668 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/service/OssTypeService.java @@ -0,0 +1,17 @@ +package kr.co.mcmp.oss.service; + +import kr.co.mcmp.oss.dto.OssTypeDto; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public interface OssTypeService { + List getAllOssTypeList(); + Long registOssType(OssTypeDto ossTypeDto); + Long updateOssType(OssTypeDto ossTypeDto); + @Transactional + Boolean deleteOssType(Long ossIdx); + OssTypeDto detailOssType(Long ossIdx); +} \ No newline at end of file diff --git a/src/main/java/kr/co/mcmp/oss/service/OssTypeServiceImpl.java b/src/main/java/kr/co/mcmp/oss/service/OssTypeServiceImpl.java new file mode 100644 index 0000000..d8b1b88 --- /dev/null +++ b/src/main/java/kr/co/mcmp/oss/service/OssTypeServiceImpl.java @@ -0,0 +1,86 @@ +package kr.co.mcmp.oss.service; + +import kr.co.mcmp.oss.dto.OssTypeDto; +import kr.co.mcmp.oss.repository.OssTypeRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Log4j2 +@RequiredArgsConstructor +@Service +public class OssTypeServiceImpl implements OssTypeService { + + private final OssTypeRepository ossTypeRepository; + + /** + * OSS Type 목록 조회 + * @return List ossTypeDtoList + */ + @Override + public List getAllOssTypeList() { + List ossTypeList = ossTypeRepository.findAll() + .stream() + .map(OssTypeDto::from) + .collect(Collectors.toList()); + return ossTypeList; + } + + /** + * OSS Type 등록 + * @param ossTypeDto + * @return + */ + @Override + public Long registOssType(OssTypeDto ossTypeDto) { + ossTypeRepository.save(OssTypeDto.toEntity(ossTypeDto)); + return ossTypeDto.getOssTypeIdx(); + } + + /** + * OSS Type 수정 + * @param ossTypeDto + * @return + */ + @Override + public Long updateOssType(OssTypeDto ossTypeDto) { + ossTypeRepository.save(OssTypeDto.toEntity(ossTypeDto)); + return ossTypeDto.getOssTypeIdx(); + } + + /** + * OSS Type삭제 + * @param ossTypeIdx + */ + @Transactional + @Override + public Boolean deleteOssType(Long ossTypeIdx) { + try { + OssTypeDto ossTypeDto = OssTypeDto.from(ossTypeRepository.findByOssTypeIdx(ossTypeIdx)); + if(ossTypeDto.getOssTypeIdx() != 0) { + ossTypeRepository.deleteById(ossTypeIdx); + } + return true; + } catch (EmptyResultDataAccessException e) { + return false; + } catch (Exception e) { + return false; + } + } + + /** + * OSS Type 상세 + * @param ossTypeIdx + */ + @Transactional + @Override + public OssTypeDto detailOssType(Long ossTypeIdx) { + return OssTypeDto.from(ossTypeRepository.findByOssTypeIdx(ossTypeIdx)); + } + +} \ No newline at end of file diff --git a/src/main/java/kr/co/mcmp/response/ResponseCode.java b/src/main/java/kr/co/mcmp/response/ResponseCode.java new file mode 100644 index 0000000..4db8fea --- /dev/null +++ b/src/main/java/kr/co/mcmp/response/ResponseCode.java @@ -0,0 +1,74 @@ +package kr.co.mcmp.response; + +import kr.co.mcmp.exception.McmpException; +import lombok.Getter; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Getter +public enum ResponseCode { + + OK(200, "정상처리 되었습니다."), + + /* common */ + BAD_REQUEST(400, "BAD REQUEST"), + UNAUTHORIZED(401, "UNAUTHORIZED"), + FORBIDDEN(403, "FORBIDDEN"), + NOT_FOUND(404, "NOT FOUND"), + METHOD_NOT_ALLOWED(405,"METHOD NOT ALLOWED"), + CLIENT_ERROR(408,"CLIENT_ERROR"), + CONFLICT(409,"CONFLICT"), + INTERNAL_SERVER_ERROR(500,"INTERNAL SERVER ERROR"), + + /* 공통 코드 */ + COMMON_CODE_EXISTS(9001, "EXISTS COMMON CODE"), + COMMON_CODE_DELETE_NOT_ALLOWED(9002,"DELETE NOT ALLOWED COMMON CODE"), + + /* Exception */ + UNKNOWN_ERROR(9999, "Unknown error"), + + /* GitLab-Project */ + ALREADY_EXISTS(1001, "ALREADY EXISTS"), + CREATE_FAILED_PROJECT_SOURCE(1003, "CREATE FAILED PROJECT SOURCE"), + + /* Jenkins */ + CREATE_FAILED_JENKINS_JOB(1103, "CREATE FAILED JENKINS JOB"), + EXISTS_JENKINS_JOB(1104, "EXISTS JENKINS JOB"), + NOT_EXISTS_JENKINS_JOB(1105, "NOT EXISTS JENKINS JOB"), + ERROR_JENKINS_API(1106, "ERROR JENKINS API"), + + /* Workload Deploy */ + RUN_FAILED_DEPLOY(1201, "RUN FAILED DEPLOY"), + + /* Catalog Deploy */ + CREATE_FAILED_APPLICATION(1301, "CREATE FAILED APPLICATION"), + NOT_EXISTS_APPLICATION(1302, "NOT EXISTS APPLICATION"), + EXISTS_APPLICATION(1303, "EXISTS APPLICATION"), + + /* K8S Client */ + CONNECTION_FAILED_CLUSTER(1401, "CONNECTION FAILED CLUSTER"), + + /* OSS */ + IN_USE_OSS(1501, "OSS IN USE CANNOT BE DELETED"), + IS_NOT_MAPPED_OSS(1502, "IS NOT MAPPED OSS TO SERVICE GROUP."), + + NEXUS_CLIENT_ERROR(1601, "NEXUS CLIENT ERROR"); + + private final int code; + private final String message; + + ResponseCode(int code, String message) { + this.code = code; + this.message = message; + } + + private static final Map map = + Stream.of(values()).collect(Collectors.toMap(ResponseCode::getCode, e -> e)); + + public static ResponseCode findByCode(int code) { + return Optional.ofNullable(map.get(code)).orElseThrow(() -> new McmpException(ResponseCode.UNKNOWN_ERROR)); + } +} diff --git a/src/main/java/kr/co/mcmp/response/ResponseWrapper.java b/src/main/java/kr/co/mcmp/response/ResponseWrapper.java new file mode 100644 index 0000000..f8f8976 --- /dev/null +++ b/src/main/java/kr/co/mcmp/response/ResponseWrapper.java @@ -0,0 +1,42 @@ +package kr.co.mcmp.response; + +import lombok.Getter; + +import java.io.Serializable; + +@Getter +public class ResponseWrapper implements Serializable { + + private static final long serialVersionUID = -1745006582949878939L; + + private int code; + private String message; + private String detail; + private T data; + + public ResponseWrapper() { + this(ResponseCode.OK); + } + + public ResponseWrapper(ResponseCode status){ + this.code = status.getCode(); + this.message = status.getMessage(); + } + + public ResponseWrapper(T data) { + this(); + this.data = data; + } + + public ResponseWrapper(ResponseCode status, String detail){ + this(status); + this.detail = detail; + } + + public ResponseWrapper(int code, String message, String detail){ + this.code = code; + this.message = message; + this.detail = detail; + } + +} diff --git a/src/main/java/kr/co/mcmp/service/oss/NexusAdapterFactory.java b/src/main/java/kr/co/mcmp/service/oss/NexusAdapterFactory.java new file mode 100644 index 0000000..df8250f --- /dev/null +++ b/src/main/java/kr/co/mcmp/service/oss/NexusAdapterFactory.java @@ -0,0 +1,25 @@ +package kr.co.mcmp.service.oss; + +import kr.co.mcmp.dto.oss.NexusFormatType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class NexusAdapterFactory { + + private final NexusRawAdapterService rawAdapterService; + private final NexusDockerAdapterService dockerAdapterService; + private final NexusHelmAdapterService helmAdapterService; + + public NexusAdapterService getAdapterService(NexusFormatType formatType) { + if ("raw".equals(formatType.getFormat())) { + return rawAdapterService; + } else if ("docker".equals(formatType.getFormat())) { + return dockerAdapterService; + } else if ("helm".equals(formatType.getFormat())) { + return helmAdapterService; + } + throw new IllegalArgumentException("unknown format: " + formatType.getFormat()); + } +} diff --git a/src/main/java/kr/co/mcmp/service/oss/NexusAdapterService.java b/src/main/java/kr/co/mcmp/service/oss/NexusAdapterService.java new file mode 100644 index 0000000..4ee4dee --- /dev/null +++ b/src/main/java/kr/co/mcmp/service/oss/NexusAdapterService.java @@ -0,0 +1,12 @@ +package kr.co.mcmp.service.oss; + +import kr.co.mcmp.dto.oss.NexusFormatType; +import kr.co.mcmp.dto.oss.NexusRepositoryDto; + +public interface NexusAdapterService { + + NexusRepositoryDto.ResGetRepositoryDto getRepositoryByName(NexusFormatType formatType, String name); + + void createRepository(NexusFormatType formatType, NexusRepositoryDto.ReqCreateRepositoryDto repositoryDto); + +} diff --git a/src/main/java/kr/co/mcmp/service/oss/NexusDockerAdapterService.java b/src/main/java/kr/co/mcmp/service/oss/NexusDockerAdapterService.java new file mode 100644 index 0000000..ccbcb10 --- /dev/null +++ b/src/main/java/kr/co/mcmp/service/oss/NexusDockerAdapterService.java @@ -0,0 +1,36 @@ +package kr.co.mcmp.service.oss; + +import kr.co.mcmp.dto.oss.NexusFormatType; +import kr.co.mcmp.dto.oss.NexusRepositoryDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.Optional; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class NexusDockerAdapterService implements NexusAdapterService { + + private final NexusRepositoryAdapterClient repositoryAdapterClient; + + @Override + public NexusRepositoryDto.ResGetRepositoryDto getRepositoryByName(NexusFormatType formatType, String name) { + return Optional.ofNullable(repositoryAdapterClient.getRepositoryByName(formatType, name)) + .orElseThrow(() -> new IllegalArgumentException("레포지토리 " + name + "을(를) 찾을 수 없습니다.")); + } + + @Override + public void createRepository(NexusFormatType formatType, NexusRepositoryDto.ReqCreateRepositoryDto repositoryDto) { + boolean duplicateNameCheck = Optional.ofNullable(repositoryAdapterClient.getRepositoryList()) + .orElse(Collections.emptyList()).stream() + .noneMatch(r -> r.getName().equals(repositoryDto.getName())); + + if (duplicateNameCheck) { + repositoryAdapterClient.createRepository(formatType, repositoryDto); + } + throw new IllegalArgumentException("중복된 이름의 레포지토리가 존재합니다."); + } +} diff --git a/src/main/java/kr/co/mcmp/service/oss/NexusHelmAdapterService.java b/src/main/java/kr/co/mcmp/service/oss/NexusHelmAdapterService.java new file mode 100644 index 0000000..396dc12 --- /dev/null +++ b/src/main/java/kr/co/mcmp/service/oss/NexusHelmAdapterService.java @@ -0,0 +1,37 @@ +package kr.co.mcmp.service.oss; + +import kr.co.mcmp.dto.oss.NexusFormatType; +import kr.co.mcmp.dto.oss.NexusRepositoryDto; +import kr.co.mcmp.exception.AlreadyExistsException; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.Optional; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class NexusHelmAdapterService implements NexusAdapterService { + + private final NexusRepositoryAdapterClient repositoryAdapterClient; + + @Override + public NexusRepositoryDto.ResGetRepositoryDto getRepositoryByName(NexusFormatType formatType, String name) { + return Optional.ofNullable(repositoryAdapterClient.getRepositoryByName(formatType, name)) + .orElseThrow(() -> new IllegalArgumentException("레포지토리 " + name + "을(를) 찾을 수 없습니다.")); + } + + @Override + public void createRepository(NexusFormatType formatType, NexusRepositoryDto.ReqCreateRepositoryDto repositoryDto) { + boolean duplicateNameCheck = Optional.ofNullable(repositoryAdapterClient.getRepositoryList()) + .orElse(Collections.emptyList()).stream() + .noneMatch(r -> r.getName().equals(repositoryDto.getName())); + + if (duplicateNameCheck) { + repositoryAdapterClient.createRepository(formatType, repositoryDto); + } + throw new IllegalArgumentException("중복된 이름의 레포지토리가 존재합니다."); + } +} diff --git a/src/main/java/kr/co/mcmp/service/oss/NexusRawAdapterService.java b/src/main/java/kr/co/mcmp/service/oss/NexusRawAdapterService.java new file mode 100644 index 0000000..5b283bd --- /dev/null +++ b/src/main/java/kr/co/mcmp/service/oss/NexusRawAdapterService.java @@ -0,0 +1,34 @@ +package kr.co.mcmp.service.oss; + +import kr.co.mcmp.dto.oss.NexusFormatType; +import kr.co.mcmp.dto.oss.NexusRepositoryDto; +import kr.co.mcmp.exception.AlreadyExistsException; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.Optional; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class NexusRawAdapterService implements NexusAdapterService { + + private final NexusRepositoryAdapterClient repositoryAdapterClient; + + @Override + public NexusRepositoryDto.ResGetRepositoryDto getRepositoryByName(NexusFormatType formatType, String name) { + return repositoryAdapterClient.getRepositoryByName(formatType, name); + } + + @Override + public void createRepository(NexusFormatType formatType, NexusRepositoryDto.ReqCreateRepositoryDto repositoryDto) { + NexusRepositoryDto.ResGetRepositoryDto repositoryOne = repositoryAdapterClient.getRepositoryOne(repositoryDto.getName()); + + if (repositoryOne != null) { + repositoryAdapterClient.createRepository(formatType, repositoryDto); + } + } + +} diff --git a/src/main/java/kr/co/mcmp/service/oss/NexusRepositoryAdapterClient.java b/src/main/java/kr/co/mcmp/service/oss/NexusRepositoryAdapterClient.java new file mode 100644 index 0000000..ec295c0 --- /dev/null +++ b/src/main/java/kr/co/mcmp/service/oss/NexusRepositoryAdapterClient.java @@ -0,0 +1,190 @@ +package kr.co.mcmp.service.oss; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.mcmp.config.oss.RestTemplateProvider; +import kr.co.mcmp.dto.oss.NexusFormatType; +import kr.co.mcmp.dto.oss.NexusRepositoryDto; +import kr.co.mcmp.exception.NexusClientException; +import kr.co.mcmp.util.Base64Util; +import lombok.extern.log4j.Log4j2; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.Collections; +import java.util.List; + +@Log4j2 +@Service +public class NexusRepositoryAdapterClient { + + private static final String NEXUS_ID = "admin"; + private static final String NEXUS_PWD = "tjxjfkxh!23"; + + private static final String BASE_URL = "http://210.217.178.130:8081/service/rest"; + private static final String AUTHORIZATION = "Authorization"; + + private static final String GET_REPO_LIST = "/v1/repositories"; + private static final String GET_REPO_ONE = "/v1/repositories/{name}"; + private static final String GET_REPO_BY_NAME = "/v1/repositories/{format}/{type}/{name}"; + private static final String POST_CREATE_REPO = "/v1/repositories/{format}/{type}"; + + public List getRepositoryList() { + String url = UriComponentsBuilder.fromHttpUrl(BASE_URL) + .path(GET_REPO_LIST) + .toUriString(); + + HttpEntity request = getRequest(null); + RestTemplate template = RestTemplateProvider.get(); + + try { + ResponseEntity> response = template.exchange( + url, + HttpMethod.GET, + request, + new ParameterizedTypeReference>() {}); + + if (response.getStatusCode() == HttpStatus.OK) { + return response.getBody(); + } else { + throw new IllegalArgumentException("Unexpected Response Status: " + response.getStatusCode() + " from URL: " + url); + } + + } catch (HttpClientErrorException e) { + String errorMessage = e.getResponseBodyAsString(); + String parseMessage = parseErrorMessage(errorMessage); + throw new IllegalArgumentException(parseMessage, e); + } + } + + public NexusRepositoryDto.ResGetRepositoryDto getRepositoryOne(String name) { + String url = UriComponentsBuilder.fromHttpUrl(BASE_URL) + .path(GET_REPO_ONE) + .buildAndExpand(name) + .toUriString(); + + HttpEntity request = getRequest(null); + RestTemplate template = RestTemplateProvider.get(); + + try { + ResponseEntity response = template.exchange( + url, + HttpMethod.GET, + request, + new ParameterizedTypeReference() {}); + + if (response.getStatusCode() == HttpStatus.OK) { + return response.getBody(); + } else { + throw new NexusClientException("Unexpected Response Status: " + response.getStatusCode() + " from URL: " + url); + } + + } catch (HttpClientErrorException e) { + String errorMessage = e.getResponseBodyAsString(); + String parseMessage = parseErrorMessage(errorMessage); + throw new NexusClientException(parseMessage); + } + } + + public NexusRepositoryDto.ResGetRepositoryDto getRepositoryByName(NexusFormatType formatType, String name) { + String url = UriComponentsBuilder.fromHttpUrl(BASE_URL) + .path(GET_REPO_BY_NAME) + .buildAndExpand(formatType.getFormat(), formatType.getType(), name) + .toUriString(); + + HttpEntity request = getRequest(null); + RestTemplate template = RestTemplateProvider.get(); + + try { + ResponseEntity response = template.exchange( + url, + HttpMethod.GET, + request, + new ParameterizedTypeReference() {}); + + if (response.getStatusCode() == HttpStatus.OK) { + return response.getBody(); + } else { + throw new NexusClientException("Unexpected Response Status: " + response.getStatusCode() + " from URL: " + url); + } + + } catch (HttpClientErrorException e) { + String errorMessage = e.getResponseBodyAsString(); + String parseMessage = parseErrorMessage(errorMessage); + throw new NexusClientException(parseMessage); + } + } + + public Object createRepository(NexusFormatType formatType, NexusRepositoryDto.ReqCreateRepositoryDto repositoryDto) { + String url = UriComponentsBuilder.fromHttpUrl(BASE_URL) + .path(POST_CREATE_REPO) + .buildAndExpand(formatType.getFormat(), formatType.getType()) + .toUriString(); + + HttpEntity request = getRequest(repositoryDto); + RestTemplate template = RestTemplateProvider.get(); + + try { + ResponseEntity response = template.exchange( + url, + HttpMethod.POST, + request, + new ParameterizedTypeReference() {}); + + if (response.getStatusCode() == HttpStatus.OK) { + return response.getBody(); + } else { + throw new IllegalArgumentException("Unexpected Response Status: " + response.getStatusCode() + " from URL: " + url); + } + + } catch (HttpClientErrorException e) { + String errorMessage = e.getResponseBodyAsString(); + String parseMessage = parseErrorMessage(errorMessage); + throw new IllegalArgumentException(parseMessage, e); + } + } + + private static HttpEntity getRequest(T body) { + String basicToken = createToken(); + HttpHeaders headers = getHeaders(basicToken); + if (body == null) { + return new HttpEntity(headers); + } + return new HttpEntity(body, headers); + } + + private static HttpHeaders getHeaders(String basicToken) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.set(AUTHORIZATION, basicToken); + return headers; + } + + private static String createToken() { + String auth = NEXUS_ID + ":" + NEXUS_PWD; + String encodedAuth = Base64Util.base64Encoding(auth); + return "Basic " + encodedAuth; + } + + private String parseErrorMessage(String errorMessage) { + ObjectMapper mapper = new ObjectMapper(); + try { + String jsonPart = errorMessage.substring(errorMessage.indexOf("{")); + + JsonNode rootNode = mapper.readTree(jsonPart); + JsonNode messageNode = rootNode.path("message"); + + String message = messageNode.asText(); + return message.replace("\\\"", "\"").replace("\"", ""); + + } catch (Exception e) { + return "Message Parsing Error"; + } + } +} diff --git a/src/main/java/kr/co/mcmp/service/oss/NexusRepositoryService.java b/src/main/java/kr/co/mcmp/service/oss/NexusRepositoryService.java new file mode 100644 index 0000000..6b1413f --- /dev/null +++ b/src/main/java/kr/co/mcmp/service/oss/NexusRepositoryService.java @@ -0,0 +1,38 @@ +package kr.co.mcmp.service.oss; + +import kr.co.mcmp.dto.oss.NexusFormatType; +import kr.co.mcmp.dto.oss.NexusRepositoryDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class NexusRepositoryService { + + private final NexusRepositoryAdapterClient repositoryAdapterClient; + private final NexusAdapterFactory adapterFactory; + + public List getRepositoryList() { + return repositoryAdapterClient.getRepositoryList(); + } + + public NexusRepositoryDto.ResGetRepositoryDto getRepositoryOne(String name) { + return repositoryAdapterClient.getRepositoryOne(name); + } + + public NexusRepositoryDto.ResGetRepositoryDto getRepositoryByName(NexusFormatType formatType, String name) { + NexusAdapterService adapterService = adapterFactory.getAdapterService(formatType); + return adapterService.getRepositoryByName(formatType, name); + } + + public void createRepository(NexusFormatType formatType, NexusRepositoryDto.ReqCreateRepositoryDto repositoryDto) { + NexusAdapterService adapterService = adapterFactory.getAdapterService(formatType); + adapterService.createRepository(formatType, repositoryDto); + } + + //public updateRepository(String name, NexusRepositoryDto.ReqUpdateRepositoryDto repositoryDto) +} From 78b391e5a3a74b50876cef6486c4867285d7af1a Mon Sep 17 00:00:00 2001 From: pbccc Date: Thu, 22 Aug 2024 15:52:28 +0900 Subject: [PATCH 3/3] test --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e5e81d0..747e1a1 100755 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ v.0.3.0(2024.10) - workflow-manager를 연동한 멀티 클라우드 인프라에 애플리케이션 배포 기능(to k8s) - k8s에 배포 시 필요한 일부 yaml generate 기능(deployment, service, pod, configmap 등) - repository 관련 제어(nexus 등) +- 기타 ## 목차