diff --git a/README.md b/README.md index ac711ef3..31a2bce5 100644 --- a/README.md +++ b/README.md @@ -106,12 +106,12 @@ Use the spring boot's dev profile to use default values that are just for non-pr #### From Command Line -1.Start with oauth +1. Start with oauth profile ```shell ./mvmw clean install -DskipTests $ java -jar target/terra-boot-*.jar\ ---spring.profiles.active=oauth \ +--spring.profiles.active=oauth,dev \ --authorization.token.type=${token-type} \ --authorization.server.endpoint=${server-endpoint} \ --authorization.api.client.id=${client-id} \ @@ -123,7 +123,7 @@ $ java -jar target/terra-boot-*.jar\ ```shell ./mvmw clean install -DskipTests -$ java -jar target/terra-boot-*.jar +$ java -jar --spring.profiles.active=dev target/terra-boot-*.jar ``` #### From IDE @@ -147,6 +147,39 @@ http://localhost:9090 http://localhost:9090/swagger-ui/index.html ``` +### AMQP support +#### Start AMQP Broker + +Use RabbitMQ as the default provider for AMQP. Start the broker and create queues using the following command: + +```shell +mkdir -p ~/docker/rabbitmq/data +docker run -d \ +--user 1000:1000 \ +--name xpanse-rabbitmq \ +-p 5672:5672 \ +-p 15672:15672 \ +-v ~/docker/rabbitmq/data:/var/lib/rabbitmq \ +-e RABBITMQ_DEFAULT_USER=xpanse \ +-e RABBITMQ_DEFAULT_PASS=Xpanse@2023 \ +rabbitmq:4.0.7-management +``` +Access the RabbitMQ management console at http://localhost:15672 with username `xpanse` and password `Xpanse@2023` after the container is started. + +#### Enable AMQP +Start the application with the `amqp` profile. + +```shell +./mvmw clean install -DskipTests +$ java -jar --spring.profiles.active=dev,amqp target/terra-boot-*.jar +``` + +#### AsyncApi Docs and UI + +When the application started with the `amqp` profile, the AsyncApi docs and UI Console are enabled. +The AsyncApi docs can be accessed at http://localhost:9090/queues/docs. +The AsyncApi UI can be accessed at http://localhost:9090/queues/asyncapi-ui.html. + ### Production #### Docker Image diff --git a/pom.xml b/pom.xml index 8c355b71..76fdebfc 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,7 @@ 3.5.2 3.5.0 2.44.2 + 1.11.0 @@ -63,6 +64,32 @@ com.github.ben-manes.caffeine caffeine + + + org.springframework.boot + spring-boot-starter-amqp + + + + io.github.springwolf + springwolf-asyncapi + ${springwolf.version} + + + io.github.springwolf + springwolf-core + ${springwolf.version} + + + io.github.springwolf + springwolf-amqp + ${springwolf.version} + + + io.github.springwolf + springwolf-ui + ${springwolf.version} + org.projectlombok lombok diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/config/JacksonConfig.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/config/JacksonConfig.java new file mode 100644 index 00000000..7d3f3465 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/config/JacksonConfig.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.api.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.eclipse.xpanse.terra.boot.models.request.TerraformRequest; +import org.eclipse.xpanse.terra.boot.models.request.TerraformRequestDeserializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; + +/** Configuration class for Jackson. */ +@Configuration +public class JacksonConfig { + + /** + * Create a ObjectMapper with the given module. + * + * @return objectMapper bean. + */ + @Bean + @Primary + public ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + SimpleModule terraformRequestModule = new SimpleModule(); + terraformRequestModule.addDeserializer( + TerraformRequest.class, new TerraformRequestDeserializer()); + mapper.registerModule(terraformRequestModule); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper; + } + + /** + * Define MappingJackson2HttpMessageConverter with the given objectMapper. + * + * @param objectMapper objectMapper + * @return mappingJackson2HttpMessageConverter bean + */ + @Bean + public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter( + ObjectMapper objectMapper) { + return new MappingJackson2HttpMessageConverter(objectMapper); + } +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootAdminApi.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootAdminApi.java index 41e70e92..0a820755 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootAdminApi.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootAdminApi.java @@ -7,11 +7,13 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; -import org.eclipse.xpanse.terra.boot.models.TerraBootSystemStatus; -import org.eclipse.xpanse.terra.boot.terraform.service.TerraformDirectoryService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; +import org.eclipse.xpanse.terra.boot.models.response.TerraBootSystemStatus; +import org.eclipse.xpanse.terra.boot.terraform.service.TerraformRequestService; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.CrossOrigin; @@ -19,22 +21,18 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; /** REST controller for admin services of terra-boot. */ @Slf4j @CrossOrigin +@Profile("!amqp") @RestController @RequestMapping("/terra-boot") public class TerraBootAdminApi { - private final TerraformDirectoryService terraformDirectoryService; - - @Autowired - public TerraBootAdminApi( - @Qualifier("terraformDirectoryService") - TerraformDirectoryService terraformDirectoryService) { - this.terraformDirectoryService = terraformDirectoryService; - } + @Resource private TerraformRequestService requestService; /** * Method to find out the current state of the system. @@ -46,6 +44,12 @@ public TerraBootAdminApi( @GetMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraBootSystemStatus healthCheck() { - return terraformDirectoryService.tfHealthCheck(); + TerraBootSystemStatus healthStatus = requestService.healthCheck(UUID.randomUUID()); + HttpServletRequest request = + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()) + .getRequest(); + healthStatus.setServiceType(request.getScheme()); + healthStatus.setServiceUrl(request.getRequestURL().toString()); + return healthStatus; } } diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromDirectoryApi.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromDirectoryApi.java index 4972e03e..1f6e3401 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromDirectoryApi.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromDirectoryApi.java @@ -5,56 +5,37 @@ package org.eclipse.xpanse.terra.boot.api.controllers; -import static org.eclipse.xpanse.terra.boot.logging.CustomRequestIdGenerator.REQUEST_ID; - import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; -import java.io.File; -import java.util.List; -import java.util.Objects; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlan; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlanFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncDeployFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncDestroyFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncModifyFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformDeployFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformDestroyFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformModifyFromDirectoryRequest; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.response.TerraformPlan; import org.eclipse.xpanse.terra.boot.models.response.TerraformResult; -import org.eclipse.xpanse.terra.boot.models.validation.TerraformValidationResult; -import org.eclipse.xpanse.terra.boot.terraform.service.TerraformDirectoryService; -import org.eclipse.xpanse.terra.boot.terraform.service.TerraformScriptsHelper; -import org.eclipse.xpanse.terra.boot.terraform.tool.TerraformVersionsHelper; -import org.slf4j.MDC; +import org.eclipse.xpanse.terra.boot.models.response.validation.TerraformValidationResult; +import org.eclipse.xpanse.terra.boot.terraform.service.TerraformRequestService; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; /** REST controller for running terraform modules directly on the provided directory. */ @Slf4j +@Profile("!amqp") @CrossOrigin @RestController @RequestMapping("/terra-boot/directory") public class TerraBootFromDirectoryApi { - @Resource private TerraformDirectoryService directoryService; - @Resource private TerraformScriptsHelper scriptsHelper; + @Resource private TerraformRequestService requestService; /** * Method to validate Terraform modules. @@ -65,26 +46,26 @@ public class TerraBootFromDirectoryApi { name = "TerraformFromDirectory", description = "APIs for running Terraform commands inside a provided directory.") @Operation(description = "Validate the Terraform modules in the given directory.") - @GetMapping( - value = "/validate/{module_directory}/{terraform_version}", - produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = "/validate", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformValidationResult validateFromDirectory( - @Parameter( - name = "module_directory", - description = "directory name where the Terraform module files exist.") - @PathVariable("module_directory") - String moduleDirectory, - @Parameter( - name = "terraform_version", - description = "version of Terraform to execute the module files.") - @NotBlank - @Pattern(regexp = TerraformVersionsHelper.TERRAFORM_REQUIRED_VERSION_REGEX) - @PathVariable("terraform_version") - String terraformVersion) { - UUID uuid = UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - return directoryService.tfValidateFromDirectory(moduleDirectory, terraformVersion); + @Valid @RequestBody TerraformRequestWithScriptsDirectory request) { + return requestService.handleTerraformValidateRequest(request); + } + + /** + * Method to get Terraform plan as a JSON string from a directory. + * + * @return Returns the terraform plan as a JSON string. + */ + @Tag( + name = "TerraformFromDirectory", + description = "APIs for running Terraform commands inside a provided directory.") + @Operation(description = "Get Terraform Plan as JSON string from the given directory.") + @PostMapping(value = "/plan", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(HttpStatus.OK) + public TerraformPlan plan(@Valid @RequestBody TerraformRequestWithScriptsDirectory request) { + return requestService.handleTerraformPlanRequest(request); } /** @@ -96,23 +77,11 @@ public TerraformValidationResult validateFromDirectory( name = "TerraformFromDirectory", description = "APIs for running Terraform commands inside a provided directory.") @Operation(description = "Deploy resources via Terraform from the given directory.") - @PostMapping(value = "/deploy/{module_directory}", produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = "/deploy", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformResult deployFromDirectory( - @Parameter( - name = "module_directory", - description = "directory name where the Terraform module files exist.") - @PathVariable("module_directory") - String moduleDirectory, - @Valid @RequestBody TerraformDeployFromDirectoryRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - List scriptFiles = scriptsHelper.getDeploymentFilesFromTaskWorkspace(moduleDirectory); - return directoryService.deployFromDirectory(request, moduleDirectory, scriptFiles); + @Valid @RequestBody TerraformRequestWithScriptsDirectory request) { + return requestService.handleTerraformDeploymentRequest(request); } /** @@ -124,23 +93,11 @@ public TerraformResult deployFromDirectory( name = "TerraformFromDirectory", description = "APIs for running Terraform commands inside a provided directory.") @Operation(description = "Modify resources via Terraform from the given directory.") - @PostMapping(value = "/modify/{module_directory}", produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = "/modify", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformResult modifyFromDirectory( - @Parameter( - name = "module_directory", - description = "directory name where the Terraform module files exist.") - @PathVariable("module_directory") - String moduleDirectory, - @Valid @RequestBody TerraformModifyFromDirectoryRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - List scriptFiles = scriptsHelper.getDeploymentFilesFromTaskWorkspace(moduleDirectory); - return directoryService.modifyFromDirectory(request, moduleDirectory, scriptFiles); + @Valid @RequestBody TerraformRequestWithScriptsDirectory request) { + return requestService.handleTerraformDeploymentRequest(request); } /** @@ -152,53 +109,11 @@ public TerraformResult modifyFromDirectory( name = "TerraformFromDirectory", description = "APIs for running Terraform commands inside a provided directory.") @Operation(description = "Destroy the resources from the given directory.") - @DeleteMapping( - value = "/destroy/{module_directory}", - produces = MediaType.APPLICATION_JSON_VALUE) + @DeleteMapping(value = "/destroy", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformResult destroyFromDirectory( - @Parameter( - name = "module_directory", - description = "directory name where the Terraform module files exist.") - @PathVariable("module_directory") - String moduleDirectory, - @Valid @RequestBody TerraformDestroyFromDirectoryRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - List scriptFiles = scriptsHelper.getDeploymentFilesFromTaskWorkspace(moduleDirectory); - return directoryService.destroyFromDirectory(request, moduleDirectory, scriptFiles); - } - - /** - * Method to get Terraform plan as a JSON string from a directory. - * - * @return Returns the terraform plan as a JSON string. - */ - @Tag( - name = "TerraformFromDirectory", - description = "APIs for running Terraform commands inside a provided directory.") - @Operation(description = "Get Terraform Plan as JSON string from the given directory.") - @PostMapping(value = "/plan/{module_directory}", produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseStatus(HttpStatus.OK) - public TerraformPlan plan( - @Parameter( - name = "module_directory", - description = "directory name where the Terraform module files exist.") - @PathVariable("module_directory") - String moduleDirectory, - @Valid @RequestBody TerraformPlanFromDirectoryRequest request, - @RequestHeader(name = "X-Custom-RequestId", required = false) UUID uuid) { - uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : (Objects.nonNull(uuid) ? uuid : UUID.randomUUID()); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return directoryService.getTerraformPlanFromDirectory(request, moduleDirectory); + @Valid @RequestBody TerraformRequestWithScriptsDirectory request) { + return requestService.handleTerraformDeploymentRequest(request); } /** Method to async deploy resources from the given directory. */ @@ -206,25 +121,11 @@ public TerraformPlan plan( name = "TerraformFromDirectory", description = "APIs for running Terraform commands inside a provided directory.") @Operation(description = "async deploy resources via Terraform from the given directory.") - @PostMapping( - value = "/deploy/async/{module_directory}", - produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = "/deploy/async", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) public void asyncDeployFromDirectory( - @Parameter( - name = "module_directory", - description = "directory name where the Terraform module files exist.") - @PathVariable("module_directory") - String moduleDirectory, - @Valid @RequestBody TerraformAsyncDeployFromDirectoryRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - List scriptFiles = scriptsHelper.getDeploymentFilesFromTaskWorkspace(moduleDirectory); - directoryService.asyncDeployWithScripts(request, moduleDirectory, scriptFiles); + @Valid @RequestBody TerraformAsyncRequestWithScriptsDirectory request) { + requestService.processAsyncDeploymentRequest(request); } /** Method to async modify resources from the given directory. */ @@ -232,25 +133,11 @@ public void asyncDeployFromDirectory( name = "TerraformFromDirectory", description = "APIs for running Terraform commands inside a provided directory.") @Operation(description = "async modify resources via Terraform from the given directory.") - @PostMapping( - value = "/modify/async/{module_directory}", - produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = "/modify/async", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) public void asyncModifyFromDirectory( - @Parameter( - name = "module_directory", - description = "directory name where the Terraform module files exist.") - @PathVariable("module_directory") - String moduleDirectory, - @Valid @RequestBody TerraformAsyncModifyFromDirectoryRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - List scriptFiles = scriptsHelper.getDeploymentFilesFromTaskWorkspace(moduleDirectory); - directoryService.asyncModifyWithScripts(request, moduleDirectory, scriptFiles); + @Valid @RequestBody TerraformAsyncRequestWithScriptsDirectory request) { + requestService.processAsyncDeploymentRequest(request); } /** Method to async destroy resources from the given directory. */ @@ -258,24 +145,10 @@ public void asyncModifyFromDirectory( name = "TerraformFromDirectory", description = "APIs for running Terraform commands inside a provided directory.") @Operation(description = "async destroy resources via Terraform from the given directory.") - @DeleteMapping( - value = "/destroy/async/{module_directory}", - produces = MediaType.APPLICATION_JSON_VALUE) + @DeleteMapping(value = "/destroy/async", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) public void asyncDestroyFromDirectory( - @Parameter( - name = "module_directory", - description = "directory name where the Terraform module files exist.") - @PathVariable("module_directory") - String moduleDirectory, - @Valid @RequestBody TerraformAsyncDestroyFromDirectoryRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - List scriptFiles = scriptsHelper.getDeploymentFilesFromTaskWorkspace(moduleDirectory); - directoryService.asyncDestroyWithScripts(request, moduleDirectory, scriptFiles); + @Valid @RequestBody TerraformAsyncRequestWithScriptsDirectory request) { + requestService.processAsyncDeploymentRequest(request); } } diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromGitRepoApi.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromGitRepoApi.java index 2689ddea..8ac6d9e3 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromGitRepoApi.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromGitRepoApi.java @@ -5,26 +5,18 @@ package org.eclipse.xpanse.terra.boot.api.controllers; -import static org.eclipse.xpanse.terra.boot.logging.CustomRequestIdGenerator.REQUEST_ID; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; import jakarta.validation.Valid; -import java.util.Objects; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlan; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlanFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncDeployFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncDestroyFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncModifyFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformDeployFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformDestroyFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformModifyFromGitRepoRequest; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.response.TerraformPlan; import org.eclipse.xpanse.terra.boot.models.response.TerraformResult; -import org.eclipse.xpanse.terra.boot.models.validation.TerraformValidationResult; -import org.eclipse.xpanse.terra.boot.terraform.service.TerraformGitRepoService; -import org.slf4j.MDC; +import org.eclipse.xpanse.terra.boot.models.response.validation.TerraformValidationResult; +import org.eclipse.xpanse.terra.boot.terraform.service.TerraformRequestService; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.CrossOrigin; @@ -38,15 +30,12 @@ /** REST controller for running terraform modules from a GIT repo. */ @Slf4j @CrossOrigin +@Profile("!amqp") @RestController @RequestMapping("/terra-boot/git") public class TerraBootFromGitRepoApi { - private final TerraformGitRepoService terraformGitRepoService; - - public TerraBootFromGitRepoApi(TerraformGitRepoService terraformGitRepoService) { - this.terraformGitRepoService = terraformGitRepoService; - } + @Resource private TerraformRequestService requestService; /** * Method to validate resources by scripts. @@ -61,14 +50,8 @@ public TerraBootFromGitRepoApi(TerraformGitRepoService terraformGitRepoService) @PostMapping(value = "/validate", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformValidationResult validateScriptsFromGitRepo( - @Valid @RequestBody TerraformDeployFromGitRepoRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformGitRepoService.validateWithScripts(request); + @Valid @RequestBody TerraformRequestWithScriptsGitRepo request) { + return requestService.handleTerraformValidateRequest(request); } /** @@ -86,14 +69,8 @@ public TerraformValidationResult validateScriptsFromGitRepo( @PostMapping(value = "/plan", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformPlan planFromGitRepo( - @Valid @RequestBody TerraformPlanFromGitRepoRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformGitRepoService.getTerraformPlanFromGitRepo(request, uuid); + @Valid @RequestBody TerraformRequestWithScriptsGitRepo request) { + return requestService.handleTerraformPlanRequest(request); } /** @@ -109,14 +86,8 @@ public TerraformPlan planFromGitRepo( @PostMapping(value = "/deploy", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformResult deployFromGitRepo( - @Valid @RequestBody TerraformDeployFromGitRepoRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformGitRepoService.deployFromGitRepo(request, uuid); + @Valid @RequestBody TerraformRequestWithScriptsGitRepo request) { + return requestService.handleTerraformDeploymentRequest(request); } /** @@ -132,14 +103,8 @@ public TerraformResult deployFromGitRepo( @PostMapping(value = "/modify", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformResult modifyFromGitRepo( - @Valid @RequestBody TerraformModifyFromGitRepoRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformGitRepoService.modifyFromGitRepo(request, uuid); + @Valid @RequestBody TerraformRequestWithScriptsGitRepo request) { + return requestService.handleTerraformDeploymentRequest(request); } /** @@ -155,14 +120,8 @@ public TerraformResult modifyFromGitRepo( @PostMapping(value = "/destroy", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformResult destroyFromGitRepo( - @Valid @RequestBody TerraformDestroyFromGitRepoRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformGitRepoService.destroyFromGitRepo(request, uuid); + @Valid @RequestBody TerraformRequestWithScriptsGitRepo request) { + return requestService.handleTerraformDeploymentRequest(request); } /** Method to async deploy resources from the provided GIT Repo. */ @@ -174,14 +133,8 @@ public TerraformResult destroyFromGitRepo( @PostMapping(value = "/deploy/async", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) public void asyncDeployFromGitRepo( - @Valid @RequestBody TerraformAsyncDeployFromGitRepoRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - terraformGitRepoService.asyncDeployFromGitRepo(request, uuid); + @Valid @RequestBody TerraformAsyncRequestWithScriptsGitRepo request) { + requestService.processAsyncDeploymentRequest(request); } /** Method to async modify resources from the provided GIT Repo. */ @@ -193,14 +146,8 @@ public void asyncDeployFromGitRepo( @PostMapping(value = "/modify/async", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) public void asyncModifyFromGitRepo( - @Valid @RequestBody TerraformAsyncModifyFromGitRepoRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - terraformGitRepoService.asyncModifyFromGitRepo(request, uuid); + @Valid @RequestBody TerraformAsyncRequestWithScriptsGitRepo request) { + requestService.processAsyncDeploymentRequest(request); } /** Method to async destroy resources by scripts. */ @@ -212,13 +159,7 @@ public void asyncModifyFromGitRepo( @DeleteMapping(value = "/destroy/async", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) public void asyncDestroyFromGitRepo( - @Valid @RequestBody TerraformAsyncDestroyFromGitRepoRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - terraformGitRepoService.asyncDestroyFromGitRepo(request, uuid); + @Valid @RequestBody TerraformAsyncRequestWithScriptsGitRepo request) { + requestService.processAsyncDeploymentRequest(request); } } diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromScriptsApi.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromScriptsApi.java index f29dff62..8cd98a92 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromScriptsApi.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootFromScriptsApi.java @@ -5,26 +5,18 @@ package org.eclipse.xpanse.terra.boot.api.controllers; -import static org.eclipse.xpanse.terra.boot.logging.CustomRequestIdGenerator.REQUEST_ID; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; import jakarta.validation.Valid; -import java.util.Objects; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlan; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlanWithScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncDeployFromScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncDestroyFromScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncModifyFromScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformDeployWithScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformDestroyWithScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformModifyWithScriptsRequest; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncRequestWithScripts; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformRequestWithScripts; +import org.eclipse.xpanse.terra.boot.models.response.TerraformPlan; import org.eclipse.xpanse.terra.boot.models.response.TerraformResult; -import org.eclipse.xpanse.terra.boot.models.validation.TerraformValidationResult; -import org.eclipse.xpanse.terra.boot.terraform.service.TerraformScriptsService; -import org.slf4j.MDC; +import org.eclipse.xpanse.terra.boot.models.response.validation.TerraformValidationResult; +import org.eclipse.xpanse.terra.boot.terraform.service.TerraformRequestService; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.CrossOrigin; @@ -38,15 +30,12 @@ /** API methods implemented by terra-boot. */ @Slf4j @CrossOrigin +@Profile("!amqp") @RestController @RequestMapping("/terra-boot/scripts/") public class TerraBootFromScriptsApi { - private final TerraformScriptsService terraformScriptsService; - - public TerraBootFromScriptsApi(TerraformScriptsService terraformScriptsService) { - this.terraformScriptsService = terraformScriptsService; - } + @Resource private TerraformRequestService requestService; /** * Method to validate resources by scripts. @@ -61,14 +50,8 @@ public TerraBootFromScriptsApi(TerraformScriptsService terraformScriptsService) @PostMapping(value = "/validate", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformValidationResult validateWithScripts( - @Valid @RequestBody TerraformDeployWithScriptsRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformScriptsService.validateWithScripts(request); + @Valid @RequestBody TerraformRequestWithScripts request) { + return requestService.handleTerraformValidateRequest(request); } /** @@ -84,14 +67,8 @@ public TerraformValidationResult validateWithScripts( @PostMapping(value = "/deploy", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformResult deployWithScripts( - @Valid @RequestBody TerraformDeployWithScriptsRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformScriptsService.deployWithScripts(request, uuid); + @Valid @RequestBody TerraformRequestWithScripts request) { + return requestService.handleTerraformDeploymentRequest(request); } /** @@ -107,14 +84,8 @@ public TerraformResult deployWithScripts( @PostMapping(value = "/modify", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformResult modifyWithScripts( - @Valid @RequestBody TerraformModifyWithScriptsRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformScriptsService.modifyWithScripts(request, uuid); + @Valid @RequestBody TerraformRequestWithScripts request) { + return requestService.handleTerraformDeploymentRequest(request); } /** @@ -130,14 +101,8 @@ public TerraformResult modifyWithScripts( @PostMapping(value = "/destroy", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public TerraformResult destroyWithScripts( - @Valid @RequestBody TerraformDestroyWithScriptsRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformScriptsService.destroyWithScripts(request, uuid); + @Valid @RequestBody TerraformRequestWithScripts request) { + return requestService.handleTerraformDeploymentRequest(request); } /** Method to async deploy resources by scripts. */ @@ -149,14 +114,8 @@ public TerraformResult destroyWithScripts( @PostMapping(value = "/deploy/async", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) public void asyncDeployWithScripts( - @Valid @RequestBody TerraformAsyncDeployFromScriptsRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - terraformScriptsService.asyncDeployWithScripts(request, uuid); + @Valid @RequestBody TerraformAsyncRequestWithScripts request) { + requestService.processAsyncDeploymentRequest(request); } /** Method to async modify resources by scripts. */ @@ -168,14 +127,8 @@ public void asyncDeployWithScripts( @PostMapping(value = "/modify/async", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) public void asyncModifyWithScripts( - @Valid @RequestBody TerraformAsyncModifyFromScriptsRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - terraformScriptsService.asyncModifyWithScripts(request, uuid); + @Valid @RequestBody TerraformAsyncRequestWithScripts request) { + requestService.processAsyncDeploymentRequest(request); } /** Method to async destroy resources by scripts. */ @@ -187,14 +140,8 @@ public void asyncModifyWithScripts( @DeleteMapping(value = "/destroy/async", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) public void asyncDestroyWithScripts( - @Valid @RequestBody TerraformAsyncDestroyFromScriptsRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - terraformScriptsService.asyncDestroyWithScripts(request, uuid); + @Valid @RequestBody TerraformAsyncRequestWithScripts request) { + requestService.processAsyncDeploymentRequest(request); } /** @@ -211,14 +158,7 @@ public void asyncDestroyWithScripts( "Get Terraform Plan as JSON string from the list of script files provided") @PostMapping(value = "/plan", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) - public TerraformPlan planWithScripts( - @Valid @RequestBody TerraformPlanWithScriptsRequest request) { - UUID uuid = - Objects.nonNull(request.getRequestId()) - ? request.getRequestId() - : UUID.randomUUID(); - MDC.put(REQUEST_ID, uuid.toString()); - request.setRequestId(uuid); - return terraformScriptsService.getTerraformPlanFromScripts(request, uuid); + public TerraformPlan planWithScripts(@Valid @RequestBody TerraformRequestWithScripts request) { + return requestService.handleTerraformPlanRequest(request); } } diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootTaskResultApi.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootTaskResultApi.java index 9a54be54..c28758cf 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootTaskResultApi.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/controllers/TerraBootTaskResultApi.java @@ -15,6 +15,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.xpanse.terra.boot.models.response.ReFetchResult; import org.eclipse.xpanse.terra.boot.terraform.service.TerraformResultPersistenceManage; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.util.CollectionUtils; @@ -29,6 +30,7 @@ /** REST controller for manage the task form terra-boot. */ @Slf4j +@Profile("!amqp") @CrossOrigin @RestController @RequestMapping("/terra-boot/task") diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/AmqpConsumer.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/AmqpConsumer.java new file mode 100644 index 00000000..d0a7e4f6 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/AmqpConsumer.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.api.queues; + +import java.util.UUID; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformRequestWithScripts; +import org.springframework.context.annotation.Profile; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +/** AMQP consumer. */ +@Profile("amqp") +@Component +public interface AmqpConsumer { + + /** + * Get terraform health check request and process it. + * + * @param requestId request id + */ + void processTerraformHealthCheckRequestFromQueue(@Payload UUID requestId); + + /** + * Get terraform request with scripts directory from queue and process it. + * + * @param request request + */ + void processTerraformRequestWithDirectoryFromQueue( + @Payload TerraformRequestWithScriptsDirectory request); + + /** + * Get terraform request with scripts directory from queue and process it. + * + * @param request request + */ + void processTerraformRequestWithGitFromQueue( + @Payload TerraformRequestWithScriptsGitRepo request); + + /** + * Get terraform request with scripts directory from queue and process it. + * + * @param request request + */ + void processTerraformRequestWithScriptsFromQueue(@Payload TerraformRequestWithScripts request); +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/AmqpProducer.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/AmqpProducer.java new file mode 100644 index 00000000..3b24dd7c --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/AmqpProducer.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.api.queues; + +import java.util.UUID; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformRequestWithScripts; +import org.eclipse.xpanse.terra.boot.models.response.TerraBootSystemStatus; +import org.eclipse.xpanse.terra.boot.models.response.TerraformPlan; +import org.eclipse.xpanse.terra.boot.models.response.TerraformResult; +import org.eclipse.xpanse.terra.boot.models.response.validation.TerraformValidationResult; +import org.springframework.context.annotation.Profile; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +/** AMQP producer. */ +@Profile("amqp") +@Component +public interface AmqpProducer { + + /** Send terraform health check request to amqp queue. */ + void sendTerraformHealthCheckRequest(@Payload UUID requestId); + + /** + * Send terraform request with scripts to amqp queue. + * + * @param request request to send. + */ + void sendTerraformRequestWithDirectory(@Payload TerraformRequestWithScriptsDirectory request); + + /** + * Send terraform request with scripts git repo to amqp queue. + * + * @param request request to send. + */ + void sendTerraformRequestWithScriptsGitRepo( + @Payload TerraformRequestWithScriptsGitRepo request); + + /** + * Send terraform request with scripts to amqp queue. + * + * @param request request to send. + */ + void sendTerraformRequestWithScripts(@Payload TerraformRequestWithScripts request); + + /** + * Send terraform health check result to amqp queue. + * + * @param result result to send. + */ + void sendTerraformHealthCheckResult(@Payload TerraBootSystemStatus result); + + /** + * Send terraform plan result to amqp queue. + * + * @param result result to send. + */ + void sendTerraformPlanResult(@Payload TerraformPlan result); + + /** + * Send terraform validation result to amqp queue. + * + * @param result result to send. + */ + void sendTerraformValidationResult(@Payload TerraformValidationResult result); + + /** + * Send terraform deployment result to amqp queue. + * + * @param result result to send. + */ + void sendTerraformDeploymentResult(@Payload TerraformResult result); +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/config/AmqpConfig.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/config/AmqpConfig.java new file mode 100644 index 00000000..b9c8b52a --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/config/AmqpConfig.java @@ -0,0 +1,175 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.api.queues.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.DirectExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +/** Configuration for Spring AMQP (Advanced Message Queuing Protocol). */ +@Slf4j +@Profile("amqp") +@Component +public class AmqpConfig { + + @Resource private ObjectMapper objectMapper; + + /** Create JSON message converter for Spring AMQP. */ + @Bean("customJsonMessageConverter") + public Jackson2JsonMessageConverter jsonMessageConverter() { + ObjectMapper localObjectMapper = objectMapper.copy(); + localObjectMapper + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .deactivateDefaultTyping() + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + Jackson2JsonMessageConverter converter = + new Jackson2JsonMessageConverter(localObjectMapper); + converter.setAlwaysConvertToInferredType(true); + return converter; + } + + private Queue createDurableQueue(String queueName) { + return QueueBuilder.durable(queueName).build(); + } + + /** Create durable queue for Terraform health check request. */ + @Bean + public Queue queueForTerraformHealthCheckRequest() { + return createDurableQueue(AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_HEALTH_CHECK_REQUEST); + } + + /** Create durable queue for Terraform request with directory. */ + @Bean + public Queue queueForTerraformRequestWithDirectory() { + return createDurableQueue(AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_DIRECTORY); + } + + /** Create durable queue for Terraform request with scripts. */ + @Bean + public Queue queueForTerraformRequestWithScripts() { + return createDurableQueue(AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_SCRIPTS); + } + + /** Create durable queue for Terraform request with git repo. */ + @Bean + public Queue queueForTerraformRequestWithScriptsGitRepo() { + return createDurableQueue(AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_GIT); + } + + /** Create durable queue for Terraform health check result. */ + @Bean + public Queue queueForTerraformHealthCheckResult() { + return createDurableQueue(AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_HEALTH_CHECK_RESULT); + } + + /** Create durable queue for Terraform plan result. */ + @Bean + public Queue queueForTerraformPlanResult() { + return createDurableQueue(AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_PLAN_RESULT); + } + + /** Create durable queue for Terraform validation result. */ + @Bean + public Queue queueForTerraformValidationResult() { + return createDurableQueue(AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_VALIDATION_RESULT); + } + + /** Create durable queue for Terraform deployment result. */ + @Bean + public Queue queueForTerraformDeploymentResult() { + return createDurableQueue(AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_DEPLOYMENT_RESULT); + } + + /** Create direct exchange for Terraform message. */ + @Bean + public DirectExchange terraformDirectExchange() { + return new DirectExchange(AmqpConstants.EXCHANGE_NAME_FOR_TERRAFORM, true, false); + } + + /** Bind Terraform health check request queue to Terraform direct exchange. */ + @Bean + public Binding bindHealthCheckRequest( + DirectExchange terraformDirectExchange, Queue queueForTerraformHealthCheckRequest) { + return BindingBuilder.bind(queueForTerraformHealthCheckRequest) + .to(terraformDirectExchange) + .with(AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_HEALTH_CHECK_REQUEST); + } + + /** Bind Terraform request with directory queue to Terraform direct exchange. */ + @Bean + public Binding bindRequestWithDirectory( + DirectExchange terraformDirectExchange, Queue queueForTerraformRequestWithDirectory) { + return BindingBuilder.bind(queueForTerraformRequestWithDirectory) + .to(terraformDirectExchange) + .with(AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_REQUEST_WITH_DIRECTORY); + } + + /** Bind Terraform request with scripts queue to Terraform direct exchange. */ + @Bean + public Binding bindRequestWithScripts( + DirectExchange terraformDirectExchange, Queue queueForTerraformRequestWithScripts) { + return BindingBuilder.bind(queueForTerraformRequestWithScripts) + .to(terraformDirectExchange) + .with(AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_REQUEST_WITH_SCRIPTS); + } + + /** Bind Terraform request with git repo queue to Terraform direct exchange. */ + @Bean + public Binding bindRequestWithGitRepo( + DirectExchange terraformDirectExchange, + Queue queueForTerraformRequestWithScriptsGitRepo) { + return BindingBuilder.bind(queueForTerraformRequestWithScriptsGitRepo) + .to(terraformDirectExchange) + .with(AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_REQUEST_WITH_GIT); + } + + /** Bind Terraform health check result queue to Terraform direct exchange. */ + @Bean + public Binding bindHealthCheckResult( + DirectExchange terraformDirectExchange, Queue queueForTerraformHealthCheckResult) { + return BindingBuilder.bind(queueForTerraformHealthCheckResult) + .to(terraformDirectExchange) + .with(AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_HEALTH_CHECK_RESULT); + } + + /** Bind Terraform plan result queue to Terraform direct exchange. */ + @Bean + public Binding bindPlanResult( + DirectExchange terraformDirectExchange, Queue queueForTerraformPlanResult) { + return BindingBuilder.bind(queueForTerraformPlanResult) + .to(terraformDirectExchange) + .with(AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_PLAN_RESULT); + } + + /** Bind Terraform validation result queue to Terraform direct exchange. */ + @Bean + public Binding bindValidationResult( + DirectExchange terraformDirectExchange, Queue queueForTerraformValidationResult) { + return BindingBuilder.bind(queueForTerraformValidationResult) + .to(terraformDirectExchange) + .with(AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_VALIDATION_RESULT); + } + + /** Bind Terraform deployment result queue to Terraform direct exchange. */ + @Bean + public Binding bindDeploymentResult( + DirectExchange terraformDirectExchange, Queue queueForTerraformDeploymentResult) { + return BindingBuilder.bind(queueForTerraformDeploymentResult) + .to(terraformDirectExchange) + .with(AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_DEPLOYMENT_RESULT); + } +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/config/AmqpConstants.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/config/AmqpConstants.java new file mode 100644 index 00000000..87f9957d --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/config/AmqpConstants.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.api.queues.config; + +/** Constants for AMQP queues, exchanges, and routing keys. */ +public class AmqpConstants { + + /** Name of the queue for Terraform health check request. */ + public static final String QUEUE_NAME_FOR_TERRAFORM_HEALTH_CHECK_REQUEST = + "org.eclipse.terra.boot.queue.request.health-check"; + + /** Name of the queue for Terraform request with scripts in directory. */ + public static final String QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_DIRECTORY = + "org.eclipse.terra.boot.queue.request.directory"; + + /** Name of the queue for Terraform request with scripts in git repo. */ + public static final String QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_GIT = + "org.eclipse.terra.boot.queue.request.git"; + + /** Name of the queue for Terraform request with scripts map. */ + public static final String QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_SCRIPTS = + "org.eclipse.terra.boot.queue.request.scripts"; + + /** Name of the queue for Terraform health check results. */ + public static final String QUEUE_NAME_FOR_TERRAFORM_HEALTH_CHECK_RESULT = + "org.eclipse.terra.boot.queue.result.health-check"; + + /** Name of the queue for Terraform health check results. */ + public static final String QUEUE_NAME_FOR_TERRAFORM_PLAN_RESULT = + "org.eclipse.terra.boot.queue.result.plan"; + + /** Name of the queue for Terraform health check results. */ + public static final String QUEUE_NAME_FOR_TERRAFORM_VALIDATION_RESULT = + "org.eclipse.terra.boot.queue.result.validation"; + + /** Name of the queue for Terraform results. */ + public static final String QUEUE_NAME_FOR_TERRAFORM_DEPLOYMENT_RESULT = + "org.eclipse.terra.boot.queue.result.deployment"; + + /** Exchange name for Terraform messages. */ + public static final String EXCHANGE_NAME_FOR_TERRAFORM = "terraform.direct.exchange"; + + /** Routing keys for Terraform health check request. */ + public static final String ROUTING_KEY_FOR_TERRAFORM_HEALTH_CHECK_REQUEST = + "request.health-check"; + + /** Routing keys for Terraform request with directory. */ + public static final String ROUTING_KEY_FOR_TERRAFORM_REQUEST_WITH_DIRECTORY = + "request.directory"; + + /** Routing keys for Terraform request with scripts. */ + public static final String ROUTING_KEY_FOR_TERRAFORM_REQUEST_WITH_SCRIPTS = "request.scripts"; + + /** Routing keys for Terraform request with git repo. */ + public static final String ROUTING_KEY_FOR_TERRAFORM_REQUEST_WITH_GIT = "request.git"; + + /** Routing keys for result of Terraform health check. */ + public static final String ROUTING_KEY_FOR_TERRAFORM_HEALTH_CHECK_RESULT = + "result.health-check"; + + /** Routing keys for result of Terraform plan. */ + public static final String ROUTING_KEY_FOR_TERRAFORM_PLAN_RESULT = "result.plan"; + + /** Routing keys for result of Terraform validation. */ + public static final String ROUTING_KEY_FOR_TERRAFORM_VALIDATION_RESULT = "result.validation"; + + /** Routing keys for result of Terraform deployment. */ + public static final String ROUTING_KEY_FOR_TERRAFORM_DEPLOYMENT_RESULT = "result.deployment"; + + private AmqpConstants() { + // Private constructor to prevent instantiation + } +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/DisableRabbitAutoConfiguration.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/DisableRabbitAutoConfiguration.java new file mode 100644 index 00000000..471fe72d --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/DisableRabbitAutoConfiguration.java @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.api.queues.rabbitmq; + +import org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +/** Disable AmqpAutoConfigurations when profile amqp is disabled. */ +@Profile("!amqp") +@Configuration +@EnableAutoConfiguration( + exclude = {RabbitAutoConfiguration.class, RabbitHealthContributorAutoConfiguration.class}) +public class DisableRabbitAutoConfiguration {} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/RabbitMqConfig.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/RabbitMqConfig.java new file mode 100644 index 00000000..7681ec44 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/RabbitMqConfig.java @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.api.queues.rabbitmq; + +import jakarta.annotation.Resource; +import org.springframework.amqp.core.AcknowledgeMode; +import org.springframework.amqp.rabbit.annotation.EnableRabbit; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.retry.backoff.ExponentialBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.stereotype.Component; + +/** RabbitMQ configuration for AMQP. */ +@Component +@Profile("amqp") +@ConditionalOnProperty(name = "spring.amqp.provider", havingValue = "rabbitmq") +@EnableRabbit +public class RabbitMqConfig { + + @Qualifier("customJsonMessageConverter") + @Resource + private Jackson2JsonMessageConverter jsonMessageConverter; + + @Value("${spring.rabbitmq.listener.simple.retry.max-attempts:3}") + private int retryMaxAttempts; + + @Value("${spring.rabbitmq.listener.simple.retry.initial-interval:5000}") + private int retryInitialInterval; + + @Value("${spring.rabbitmq.listener.simple.retry.max-interval:30000}") + private int retryMaxInterval; + + /** + * Create a RabbitTemplate bean with custom message converter and retry template. + * + * @param connectionFactory The connection factory used to create the RabbitTemplate. + * @return RabbitTemplate bean. + */ + @Bean("customRabbitTemplate") + public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { + final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMessageConverter(jsonMessageConverter); + rabbitTemplate.setRetryTemplate(retryTemplate()); + return rabbitTemplate; + } + + /** + * Create a RabbitListenerContainerFactory bean with custom message converter and retry + * template. + * + * @param connectionFactory The connection factory used to create the + * RabbitListenerContainerFactory. + * @return rabbitListenerContainerFactory bean. + */ + @Bean("customRabbitListenerContainerFactory") + public RabbitListenerContainerFactory + rabbitListenerContainerFactory(ConnectionFactory connectionFactory) { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setMessageConverter(jsonMessageConverter); + factory.setConnectionFactory(connectionFactory); + factory.setAcknowledgeMode(AcknowledgeMode.AUTO); + factory.setDefaultRequeueRejected(true); + factory.setFailedDeclarationRetryInterval(5000L); + factory.setPrefetchCount(100); + factory.setRetryTemplate(retryTemplate()); + + factory.setBatchSize(10); + factory.setConcurrentConsumers(5); + factory.setMaxConcurrentConsumers(10); + factory.setConsecutiveActiveTrigger(5); + factory.setConsecutiveIdleTrigger(10); + return factory; + } + + private RetryTemplate retryTemplate() { + RetryTemplate retryTemplate = new RetryTemplate(); + retryTemplate.setRetryPolicy(new SimpleRetryPolicy(retryMaxAttempts)); + ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); + backOffPolicy.setInitialInterval(retryInitialInterval); + backOffPolicy.setMaxInterval(retryMaxInterval); + retryTemplate.setBackOffPolicy(backOffPolicy); + return retryTemplate; + } +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/RabbitMqConsumer.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/RabbitMqConsumer.java new file mode 100644 index 00000000..a6b129d6 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/RabbitMqConsumer.java @@ -0,0 +1,152 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.api.queues.rabbitmq; + +import jakarta.annotation.Resource; +import java.util.Objects; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.xpanse.terra.boot.api.queues.AmqpConsumer; +import org.eclipse.xpanse.terra.boot.api.queues.config.AmqpConstants; +import org.eclipse.xpanse.terra.boot.models.enums.HealthStatus; +import org.eclipse.xpanse.terra.boot.models.exceptions.UnsupportedEnumValueException; +import org.eclipse.xpanse.terra.boot.models.request.TerraformRequest; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformRequestWithScripts; +import org.eclipse.xpanse.terra.boot.models.response.TerraBootSystemStatus; +import org.eclipse.xpanse.terra.boot.terraform.service.TerraformRequestService; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Profile; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +/** Implementation of AmqpConsumer interface for RabbitMQ. */ +@Slf4j +@Component +@Profile("amqp") +@ConditionalOnProperty(name = "spring.amqp.provider", havingValue = "rabbitmq") +public class RabbitMqConsumer implements AmqpConsumer { + + @Resource private RabbitMqProducer producer; + + @Lazy @Resource private TerraformRequestService requestService; + + @Value("${springwolf.docket.servers.amqp-server.protocol}") + private String serverProtocol; + + @Value("${springwolf.docket.servers.amqp-server.host}") + private String serverUrl; + + @RabbitListener( + queues = AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_HEALTH_CHECK_REQUEST, + containerFactory = "customRabbitListenerContainerFactory") + @Override + public void processTerraformHealthCheckRequestFromQueue(@Payload UUID requestId) { + log.info("Processing received health check request with id {}", requestId); + TerraBootSystemStatus result = null; + try { + result = requestService.healthCheck(requestId); + } catch (Exception e) { + log.error("Failed to process health request with id {}", requestId, e); + result = new TerraBootSystemStatus(); + result.setRequestId(requestId); + result.setHealthStatus(HealthStatus.NOK); + result.setErrorMessage(e.getMessage()); + } + result.setServiceType(serverProtocol); + result.setServiceUrl(serverUrl); + producer.sendTerraformHealthCheckResult(result); + } + + @RabbitListener( + queues = AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_DIRECTORY, + containerFactory = "customRabbitListenerContainerFactory") + @Override + public void processTerraformRequestWithDirectoryFromQueue( + @Payload TerraformRequestWithScriptsDirectory request) { + handleTerraformRequestAndSendResult(request); + } + + @RabbitListener( + queues = AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_GIT, + containerFactory = "customRabbitListenerContainerFactory") + @Override + public void processTerraformRequestWithGitFromQueue( + @Payload TerraformRequestWithScriptsGitRepo request) { + handleTerraformRequestAndSendResult(request); + } + + @RabbitListener( + queues = AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_SCRIPTS, + containerFactory = "customRabbitListenerContainerFactory") + @Override + public void processTerraformRequestWithScriptsFromQueue( + @Payload TerraformRequestWithScripts request) { + handleTerraformRequestAndSendResult(request); + } + + private void handleTerraformRequestAndSendResult(TerraformRequest request) { + try { + processRequestByType(request); + } catch (Exception e) { + sendErrorResultToQueue(Objects.requireNonNull(request), e); + } + } + + private void processRequestByType(TerraformRequest request) { + switch (request.getRequestType()) { + case VALIDATE -> + producer.sendTerraformValidationResult( + requestService.handleTerraformValidateRequest(request)); + case PLAN -> + producer.sendTerraformPlanResult( + requestService.handleTerraformPlanRequest(request)); + case DEPLOY, MODIFY, DESTROY -> + producer.sendTerraformDeploymentResult( + requestService.handleTerraformDeploymentRequest(request)); + default -> + throw new UnsupportedEnumValueException( + String.format( + "RequestType value %s is not supported.", + request.getRequestType().toValue())); + } + } + + private void sendErrorResultToQueue(TerraformRequest request, Exception e) { + log.error( + "Failed to process request with id {} from amqp queues. {}", + request.getRequestId(), + e.getMessage(), + e); + try { + processErrorByRequestType(request, e); + } catch (Exception innerEx) { + log.error("Error handling failed request: {}", innerEx.getMessage(), innerEx); + } + } + + private void processErrorByRequestType(TerraformRequest request, Exception e) { + switch (request.getRequestType()) { + case VALIDATE -> + producer.sendTerraformValidationResult( + requestService.getErrorValidateResult(request, e)); + case PLAN -> + producer.sendTerraformPlanResult(requestService.getErrorPlanResult(request, e)); + case DEPLOY, MODIFY, DESTROY -> + producer.sendTerraformDeploymentResult( + requestService.getErrorDeploymentResult(request, e)); + default -> + throw new UnsupportedEnumValueException( + String.format( + "RequestType value %s is not supported.", + request.getRequestType().toValue())); + } + } +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/RabbitMqProducer.java b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/RabbitMqProducer.java new file mode 100644 index 00000000..52b34c75 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/api/queues/rabbitmq/RabbitMqProducer.java @@ -0,0 +1,176 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.api.queues.rabbitmq; + +import io.github.springwolf.bindings.amqp.annotations.AmqpAsyncOperationBinding; +import io.github.springwolf.core.asyncapi.annotations.AsyncOperation; +import io.github.springwolf.core.asyncapi.annotations.AsyncPublisher; +import jakarta.annotation.Resource; +import java.util.Objects; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.xpanse.terra.boot.api.queues.AmqpProducer; +import org.eclipse.xpanse.terra.boot.api.queues.config.AmqpConstants; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformRequestWithScripts; +import org.eclipse.xpanse.terra.boot.models.response.TerraBootSystemStatus; +import org.eclipse.xpanse.terra.boot.models.response.TerraformPlan; +import org.eclipse.xpanse.terra.boot.models.response.TerraformResult; +import org.eclipse.xpanse.terra.boot.models.response.validation.TerraformValidationResult; +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Profile; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +/** implementation of AmqpProducer interface for RabbitMQ. */ +@Slf4j +@Component +@Profile("amqp") +@ConditionalOnProperty(name = "spring.amqp.provider", havingValue = "rabbitmq") +public class RabbitMqProducer implements AmqpProducer { + + @Qualifier("customRabbitTemplate") + @Resource + private RabbitTemplate rabbitTemplate; + + @AsyncPublisher( + operation = + @AsyncOperation( + channelName = + AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_HEALTH_CHECK_REQUEST, + description = "Send terraform health check request to rabbitmq queue.")) + @AmqpAsyncOperationBinding + @Override + public void sendTerraformHealthCheckRequest(@Payload UUID requestId) { + log.info("Received terraform health check request with id {}", requestId); + sendMessageViaExchange( + AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_HEALTH_CHECK_REQUEST, + requestId, + "Health check request " + requestId); + } + + @AsyncPublisher( + operation = + @AsyncOperation( + channelName = + AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_DIRECTORY, + description = "Send terraform request with scripts to rabbitmq queue.")) + @AmqpAsyncOperationBinding + @Override + public void sendTerraformRequestWithDirectory( + @Payload TerraformRequestWithScriptsDirectory request) { + log.info( + "Received terraform directory request with type {} and id {}", + request.getRequestType(), + request.getRequestId()); + sendMessageViaExchange( + AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_REQUEST_WITH_DIRECTORY, + request, + "Terraform directory request " + request.getRequestId()); + } + + @AsyncPublisher( + operation = + @AsyncOperation( + channelName = + AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_SCRIPTS, + description = "Send terraform request with scripts to rabbitmq queue.")) + @AmqpAsyncOperationBinding + @Override + public void sendTerraformRequestWithScripts(@Payload TerraformRequestWithScripts request) { + log.info( + "Received terraform scripts request with type {} and id {}", + request.getRequestType(), + request.getRequestId()); + sendMessageViaExchange( + AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_REQUEST_WITH_SCRIPTS, + request, + "Terraform scripts request " + request.getRequestId()); + } + + @AsyncPublisher( + operation = + @AsyncOperation( + channelName = AmqpConstants.QUEUE_NAME_FOR_TERRAFORM_REQUEST_WITH_GIT, + description = + "Send terraform request with scripts git repo to rabbitmq" + + " queue.")) + @AmqpAsyncOperationBinding + @Override + public void sendTerraformRequestWithScriptsGitRepo( + @Payload TerraformRequestWithScriptsGitRepo request) { + log.info( + "Received terraform git request with type {} and id {}", + request.getRequestType(), + request.getRequestId()); + sendMessageViaExchange( + AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_REQUEST_WITH_GIT, + request, + "Terraform git request " + request.getRequestId()); + } + + @Override + public void sendTerraformHealthCheckResult(@Payload TerraBootSystemStatus result) { + sendMessageViaExchange( + AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_HEALTH_CHECK_RESULT, + result, + "Terraform health check result " + result.getRequestId()); + } + + @Override + public void sendTerraformPlanResult(@Payload TerraformPlan result) { + sendMessageViaExchange( + AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_PLAN_RESULT, + result, + "Terraform plan result " + result.getRequestId()); + } + + @Override + public void sendTerraformValidationResult(@Payload TerraformValidationResult result) { + sendMessageViaExchange( + AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_VALIDATION_RESULT, + result, + "Terraform validation result " + result.getRequestId()); + } + + @Override + public void sendTerraformDeploymentResult(@Payload TerraformResult result) { + sendMessageViaExchange( + AmqpConstants.ROUTING_KEY_FOR_TERRAFORM_DEPLOYMENT_RESULT, + result, + "Terraform deployment result " + result.getRequestId()); + } + + private void sendMessageViaExchange(String routingKey, T message, String logPrefix) { + try { + if (Objects.isNull(message)) { + throw new IllegalArgumentException("Message cannot be null"); + } + if (StringUtils.isBlank(routingKey)) { + throw new IllegalArgumentException("Routing key cannot be empty"); + } + rabbitTemplate.convertAndSend( + AmqpConstants.EXCHANGE_NAME_FOR_TERRAFORM, routingKey, message); + log.debug( + "{} sent via exchange {} with key {} completed", + logPrefix, + AmqpConstants.EXCHANGE_NAME_FOR_TERRAFORM, + routingKey); + } catch (AmqpException e) { + String errorMsg = + String.format( + "Failed to send message to exchange: %s (key: %s). Error: %s", + AmqpConstants.EXCHANGE_NAME_FOR_TERRAFORM, routingKey, e.getMessage()); + log.error("{}", errorMsg, e); + throw new AmqpException(errorMsg, e); + } + } +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/TerraBootSystemStatus.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/TerraBootSystemStatus.java deleted file mode 100644 index ef8b0b69..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/TerraBootSystemStatus.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import org.eclipse.xpanse.terra.boot.models.enums.HealthStatus; - -/** Describes health status of the system. */ -@Data -public class TerraBootSystemStatus { - - @NotNull - @Schema(description = "The health status of terra-boot api service.") - private HealthStatus healthStatus; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/enums/RequestType.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/enums/RequestType.java new file mode 100644 index 00000000..69c25611 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/enums/RequestType.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.models.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.xpanse.terra.boot.models.exceptions.UnsupportedEnumValueException; + +/** The types of terraform request. */ +public enum RequestType { + VALIDATE("validate"), + PLAN("plan"), + DEPLOY("deploy"), + MODIFY("modify"), + DESTROY("destroy"); + + private final String type; + + RequestType(String type) { + this.type = type; + } + + /** Convert string to RequestType. */ + @JsonCreator + public RequestType getByValue(String value) { + for (RequestType type : values()) { + if (StringUtils.equalsIgnoreCase(type.type, value)) { + return type; + } + } + throw new UnsupportedEnumValueException( + String.format("RequestType value %s is not supported.", value)); + } + + /** For RequestType deserialize. */ + @JsonValue + public String toValue() { + return this.type; + } +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/TerraformHealthCheckException.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/InvalidTerraformRequestException.java similarity index 63% rename from src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/TerraformHealthCheckException.java rename to src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/InvalidTerraformRequestException.java index 48ba0b80..871f71b2 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/TerraformHealthCheckException.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/InvalidTerraformRequestException.java @@ -6,9 +6,9 @@ package org.eclipse.xpanse.terra.boot.models.exceptions; /** Used to indicate Terraform health check anomalies. */ -public class TerraformHealthCheckException extends RuntimeException { +public class InvalidTerraformRequestException extends RuntimeException { - public TerraformHealthCheckException(String message) { + public InvalidTerraformRequestException(String message) { super(message); } } diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/TerraformApiExceptionHandler.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/TerraformApiExceptionHandler.java index f612d87a..b65e1cf7 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/TerraformApiExceptionHandler.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/exceptions/TerraformApiExceptionHandler.java @@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.xpanse.terra.boot.models.response.Response; import org.eclipse.xpanse.terra.boot.models.response.ResultType; +import org.springframework.amqp.AmqpException; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.validation.BindingResult; @@ -42,6 +43,7 @@ public Response handleIllegalArgumentException(IllegalArgumentException ex) { @ResponseStatus(HttpStatus.BAD_GATEWAY) @ResponseBody public Response handleTerraformExecutorException(TerraformExecutorException ex) { + log.error("handleTerraformExecutorException: ", ex); return Response.errorResponse( ResultType.TERRAFORM_EXECUTION_FAILED, Collections.singletonList(ex.getMessage())); } @@ -51,6 +53,7 @@ public Response handleTerraformExecutorException(TerraformExecutorException ex) @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) @ResponseBody public Response handleUnsupportedEnumValueException(UnsupportedEnumValueException ex) { + log.error("handleUnsupportedEnumValueException: ", ex); return Response.errorResponse( ResultType.UNSUPPORTED_ENUM_VALUE, Collections.singletonList(ex.getMessage())); } @@ -61,6 +64,7 @@ public Response handleUnsupportedEnumValueException(UnsupportedEnumValueExceptio @ResponseBody public Response handleMethodArgumentTypeMismatchException( MethodArgumentTypeMismatchException ex) { + log.error("handleMethodArgumentTypeMismatchException: ", ex); return Response.errorResponse( ResultType.UNPROCESSABLE_ENTITY, Collections.singletonList(ex.getMessage())); } @@ -89,16 +93,6 @@ public Response handleHttpMessageConversionException(HttpMessageConversionExcept ResultType.BAD_PARAMETERS, Collections.singletonList(failMessage)); } - /** Exception handler for TerraformHealthCheckException. */ - @ExceptionHandler({TerraformHealthCheckException.class}) - @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) - public Response handleTerraformHealthCheckException(TerraformHealthCheckException ex) { - log.error("TerraformHealthCheckException: ", ex); - String failMessage = ex.getMessage(); - return Response.errorResponse( - ResultType.SERVICE_UNAVAILABLE, Collections.singletonList(failMessage)); - } - /** Exception handler for GitRepoCloneException. */ @ExceptionHandler({GitRepoCloneException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) @@ -114,6 +108,7 @@ public Response handleGitRepoCloneException(GitRepoCloneException ex) { @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public Response handleInvalidTerraformToolException(InvalidTerraformToolException ex) { + log.error("handleInvalidTerraformToolException: ", ex); return Response.errorResponse( ResultType.INVALID_TERRAFORM_TOOL, Collections.singletonList(ex.getMessage())); } @@ -123,7 +118,28 @@ public Response handleInvalidTerraformToolException(InvalidTerraformToolExceptio @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public Response handleInvalidTerraformScriptsException(InvalidTerraformScriptsException ex) { + log.error("handleInvalidTerraformScriptsException: ", ex); return Response.errorResponse( ResultType.INVALID_TERRAFORM_SCRIPTS, Collections.singletonList(ex.getMessage())); } + + /** Exception handler for InvalidTerraformRequestException. */ + @ExceptionHandler({InvalidTerraformRequestException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public Response handleInvalidTerraformRequestException(InvalidTerraformRequestException ex) { + log.error("handleInvalidTerraformRequestException: ", ex); + return Response.errorResponse( + ResultType.INVALID_TERRAFORM_REQUEST, Collections.singletonList(ex.getMessage())); + } + + /** Exception handler for AmqpException. */ + @ExceptionHandler({AmqpException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public Response handleAmqpException(AmqpException ex) { + log.error("handleAmqpException: ", ex); + return Response.errorResponse( + ResultType.INVALID_TERRAFORM_REQUEST, Collections.singletonList(ex.getMessage())); + } } diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlan.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlan.java deleted file mode 100644 index 25c2362d..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlan.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.plan; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Builder; -import lombok.Data; - -/** Data model to represent terraform plan output. */ -@Data -@Builder -public class TerraformPlan { - - @NotNull - @Schema(description = "Terraform plan as a JSON string") - private String plan; - - @Schema(description = "The version of the Terraform binary used to execute scripts.") - private String terraformVersionUsed; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlanFromDirectoryRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlanFromDirectoryRequest.java deleted file mode 100644 index a1a4607f..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlanFromDirectoryRequest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.plan; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import lombok.Data; -import org.eclipse.xpanse.terra.boot.terraform.tool.TerraformVersionsHelper; - -/** Data model for the generating terraform plan. */ -@Data -public class TerraformPlanFromDirectoryRequest { - - @Schema(description = "Id of the request.") - private UUID requestId; - - @NotNull - @NotBlank - @Pattern(regexp = TerraformVersionsHelper.TERRAFORM_REQUIRED_VERSION_REGEX) - @Schema(description = "The required version of terraform which will execute the scripts.") - private String terraformVersion; - - @NotNull - @Schema( - description = - "Key-value pairs of variables that must be used to execute the " - + "Terraform request.") - private Map variables; - - @Schema( - description = - "Key-value pairs of variables that must be injected as environment " - + "variables to terraform process.") - private Map envVariables = new HashMap<>(); -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlanFromGitRepoRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlanFromGitRepoRequest.java deleted file mode 100644 index e6900b17..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlanFromGitRepoRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.plan; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformScriptGitRepoDetails; - -/** Data model for the generating terraform plan using Terraform scripts from a GIT repo. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformPlanFromGitRepoRequest extends TerraformPlanFromDirectoryRequest { - - @Schema(description = "GIT Repo details from where the scripts can be fetched.") - private TerraformScriptGitRepoDetails gitRepoDetails; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlanWithScriptsRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlanWithScriptsRequest.java deleted file mode 100644 index 25825e65..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/plan/TerraformPlanWithScriptsRequest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.plan; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.Map; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** Data model for the generating terraform plan. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformPlanWithScriptsRequest extends TerraformPlanFromDirectoryRequest { - - @NotNull - @NotEmpty - @Schema( - description = - "Map stores file name and content of all script files for generating terraform" - + " plan.") - private Map scriptFiles; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/TerraformRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/TerraformRequest.java new file mode 100644 index 00000000..8af21d93 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/TerraformRequest.java @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.models.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import java.io.Serial; +import java.io.Serializable; +import java.util.Map; +import java.util.UUID; +import lombok.Data; +import org.eclipse.xpanse.terra.boot.models.enums.RequestType; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncRequestWithScripts; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformRequestWithScripts; +import org.eclipse.xpanse.terra.boot.terraform.tool.TerraformVersionsHelper; + +/** Data model for the Terraform request. */ +@Data +@Schema( + description = "Terraform request base", + subTypes = { + TerraformRequestWithScriptsDirectory.class, + TerraformAsyncRequestWithScriptsDirectory.class, + TerraformRequestWithScripts.class, + TerraformAsyncRequestWithScripts.class, + TerraformRequestWithScriptsGitRepo.class, + TerraformAsyncRequestWithScriptsGitRepo.class, + }) +public abstract class TerraformRequest implements Serializable { + + @Serial private static final long serialVersionUID = 10696793105264423L; + + @Schema(description = "Id of the request.") + @NotNull + private UUID requestId; + + @NotNull + @Schema(description = "Type of the terraform request.") + private RequestType requestType; + + @NotNull + @NotBlank + @Pattern(regexp = TerraformVersionsHelper.TERRAFORM_REQUIRED_VERSION_REGEX) + @Schema(description = "The required version of terraform which will execute the scripts.") + private String terraformVersion; + + @NotNull + @Schema( + description = + "Flag to control if the deployment must only generate the terraform " + + "or it must also apply the changes.") + private Boolean isPlanOnly; + + @NotNull + @Schema( + description = + "Key-value pairs of variables that must be used to execute the " + + "Terraform request.") + private Map variables; + + @Schema( + description = + "Key-value pairs of variables that must be injected as environment " + + "variables to terraform process.") + private Map envVariables; + + @Schema(description = "Terraform state as a string.") + private String tfState; +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/TerraformRequestDeserializer.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/TerraformRequestDeserializer.java new file mode 100644 index 00000000..f8ad23e9 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/TerraformRequestDeserializer.java @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.models.request; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncRequestWithScripts; + +/** TerraformRequestDeserializer is used to deserialize TerraformRequest. */ +@Slf4j +public class TerraformRequestDeserializer extends StdDeserializer { + + /** Constructs a new TerraformRequestDeserializer. */ + public TerraformRequestDeserializer() { + super(TerraformRequest.class); + } + + @Override + public TerraformRequest deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException { + JsonNode node = p.getCodec().readTree(p); + if (node.has("scriptFiles")) { + if (node.has("webhookConfig")) { + return p.getCodec().treeToValue(node, TerraformAsyncRequestWithScripts.class); + } + return p.getCodec().treeToValue(node, TerraformAsyncRequestWithScripts.class); + } else if (node.has("gitRepoDetails")) { + if (node.has("webhookConfig")) { + return p.getCodec() + .treeToValue(node, TerraformAsyncRequestWithScriptsGitRepo.class); + } + return p.getCodec().treeToValue(node, TerraformRequestWithScriptsGitRepo.class); + } else if (node.has("scriptsDirectory")) { + if (node.has("webhookConfig")) { + return p.getCodec() + .treeToValue(node, TerraformAsyncRequestWithScriptsDirectory.class); + } + return p.getCodec().treeToValue(node, TerraformRequestWithScriptsDirectory.class); + } + return p.getCodec().treeToValue(node, TerraformRequest.class); + } +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncDestroyFromDirectoryRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncDestroyFromDirectoryRequest.java deleted file mode 100644 index 43725005..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncDestroyFromDirectoryRequest.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.directory; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.webhook.WebhookConfig; - -/** Data model for the terraform async destroy requests. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformAsyncDestroyFromDirectoryRequest - extends TerraformDestroyFromDirectoryRequest { - - @NotNull - @Schema(description = "Configuration information of webhook.") - private WebhookConfig webhookConfig; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncModifyFromDirectoryRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncModifyFromDirectoryRequest.java deleted file mode 100644 index 05afb230..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncModifyFromDirectoryRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.directory; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.webhook.WebhookConfig; - -/** Data model for the terraform async modify requests. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformAsyncModifyFromDirectoryRequest extends TerraformModifyFromDirectoryRequest { - - @NotNull - @Schema(description = "Configuration information of webhook.") - private WebhookConfig webhookConfig; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncDeployFromDirectoryRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncRequestWithScriptsDirectory.java similarity index 53% rename from src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncDeployFromDirectoryRequest.java rename to src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncRequestWithScriptsDirectory.java index e9c293b5..3a8ec12e 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncDeployFromDirectoryRequest.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformAsyncRequestWithScriptsDirectory.java @@ -7,14 +7,23 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import lombok.Data; import lombok.EqualsAndHashCode; import org.eclipse.xpanse.terra.boot.models.request.webhook.WebhookConfig; -/** Data model for the terraform async deploy requests. */ +/** + * The terraform async request for executing command based on the directory where the scripts file + * exist. + */ @EqualsAndHashCode(callSuper = true) @Data -public class TerraformAsyncDeployFromDirectoryRequest extends TerraformDeployFromDirectoryRequest { +@Schema(description = "Terraform async request with directory where scripts files exist") +public class TerraformAsyncRequestWithScriptsDirectory extends TerraformRequestWithScriptsDirectory + implements Serializable { + + @Serial private static final long serialVersionUID = 3604091444011192314L; @NotNull @Schema(description = "Configuration information of webhook.") diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformDeployFromDirectoryRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformDeployFromDirectoryRequest.java deleted file mode 100644 index f723e81c..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformDeployFromDirectoryRequest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.directory; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import lombok.Data; -import org.eclipse.xpanse.terra.boot.terraform.tool.TerraformVersionsHelper; - -/** Data model for the terraform deploy requests. */ -@Data -public class TerraformDeployFromDirectoryRequest { - - @Schema(description = "Id of the request") - private UUID requestId; - - @NotNull - @NotBlank - @Pattern(regexp = TerraformVersionsHelper.TERRAFORM_REQUIRED_VERSION_REGEX) - @Schema(description = "The required version of terraform which will execute the scripts.") - private String terraformVersion; - - @NotNull - @Schema( - description = - "Flag to control if the deployment must only generate the terraform " - + "or it must also apply the changes.") - private Boolean isPlanOnly; - - @NotNull - @Schema( - description = - "Key-value pairs of variables that must be used to execute the " - + "Terraform request.") - private Map variables; - - @Schema( - description = - "Key-value pairs of variables that must be injected as environment " - + "variables to terraform process.") - private Map envVariables = new HashMap<>(); -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformDestroyFromDirectoryRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformDestroyFromDirectoryRequest.java deleted file mode 100644 index b3957b7d..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformDestroyFromDirectoryRequest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.directory; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import lombok.Data; -import org.eclipse.xpanse.terra.boot.terraform.tool.TerraformVersionsHelper; - -/** Data model for the terraform destroy requests. */ -@Data -public class TerraformDestroyFromDirectoryRequest { - - @Schema(description = "Id of the request") - private UUID requestId; - - @NotNull - @NotBlank - @Pattern(regexp = TerraformVersionsHelper.TERRAFORM_REQUIRED_VERSION_REGEX) - @Schema(description = "The required version of terraform which will execute the scripts.") - private String terraformVersion; - - @NotNull - @Schema( - description = - "Key-value pairs of regular variables that must be used to execute the " - + "Terraform request.") - private Map variables; - - @Schema( - description = - "Key-value pairs of variables that must be injected as environment " - + "variables to terraform process.") - private Map envVariables = new HashMap<>(); -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformModifyFromDirectoryRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformModifyFromDirectoryRequest.java deleted file mode 100644 index e9ab9749..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformModifyFromDirectoryRequest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.directory; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import lombok.Data; -import org.eclipse.xpanse.terra.boot.terraform.tool.TerraformVersionsHelper; - -/** Data model for the terraform modify requests. */ -@Data -public class TerraformModifyFromDirectoryRequest { - - @Schema(description = "Id of the request") - private UUID requestId; - - @NotNull - @NotBlank - @Pattern(regexp = TerraformVersionsHelper.TERRAFORM_REQUIRED_VERSION_REGEX) - @Schema(description = "The required version of terraform which will execute the scripts.") - private String terraformVersion; - - @NotNull - @Schema( - description = - "Flag to control if the deployment must only generate the terraform " - + "or it must also apply the changes.") - private Boolean isPlanOnly; - - @NotNull - @Schema( - description = - "Key-value pairs of regular variables that must be used to execute the " - + "Terraform request.") - private Map variables; - - @Schema( - description = - "Key-value pairs of variables that must be injected as environment " - + "variables to terraform process.") - private Map envVariables = new HashMap<>(); -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformRequestWithScriptsDirectory.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformRequestWithScriptsDirectory.java new file mode 100644 index 00000000..b77d21e0 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/directory/TerraformRequestWithScriptsDirectory.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.models.request.directory; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.io.File; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.eclipse.xpanse.terra.boot.models.request.TerraformRequest; + +/** + * The terraform request for executing command based on the directory where the scripts file exist. + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(description = "Terraform request with directory where scripts files exist") +public class TerraformRequestWithScriptsDirectory extends TerraformRequest { + + @NotNull + @NotBlank + @Schema(description = "Directory where the Terraform scripts files exist.") + private String scriptsDirectory; + + @Hidden @JsonIgnore private List scriptFiles; +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncDeployFromGitRepoRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncDeployFromGitRepoRequest.java deleted file mode 100644 index bc043535..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncDeployFromGitRepoRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.git; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.webhook.WebhookConfig; - -/** Data model for the terraform async deploy requests. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformAsyncDeployFromGitRepoRequest extends TerraformDeployFromGitRepoRequest { - - @NotNull - @Schema(description = "Configuration information of webhook.") - private WebhookConfig webhookConfig; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncModifyFromGitRepoRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncModifyFromGitRepoRequest.java deleted file mode 100644 index 4ac048f0..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncModifyFromGitRepoRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.git; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.webhook.WebhookConfig; - -/** Data model for terraform async modify requests using scripts from a GIT Repo. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformAsyncModifyFromGitRepoRequest extends TerraformModifyFromGitRepoRequest { - - @NotNull - @Schema(description = "Configuration information of webhook.") - private WebhookConfig webhookConfig; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncDestroyFromGitRepoRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncRequestWithScriptsGitRepo.java similarity index 56% rename from src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncDestroyFromGitRepoRequest.java rename to src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncRequestWithScriptsGitRepo.java index ab1e7b3f..a16bb200 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncDestroyFromGitRepoRequest.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformAsyncRequestWithScriptsGitRepo.java @@ -7,14 +7,20 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import lombok.Data; import lombok.EqualsAndHashCode; import org.eclipse.xpanse.terra.boot.models.request.webhook.WebhookConfig; -/** Data model for terraform async destroy requests using scripts from a GIT Repo. */ +/** The terraform async request for executing command based on the scripts git repo details. */ @EqualsAndHashCode(callSuper = true) @Data -public class TerraformAsyncDestroyFromGitRepoRequest extends TerraformDestroyFromGitRepoRequest { +@Schema(description = "Terraform request with scripts git repo") +public class TerraformAsyncRequestWithScriptsGitRepo extends TerraformRequestWithScriptsGitRepo + implements Serializable { + + @Serial private static final long serialVersionUID = 6509125273252260415L; @NotNull @Schema(description = "Configuration information of webhook.") diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformDeployFromGitRepoRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformDeployFromGitRepoRequest.java deleted file mode 100644 index ebafa321..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformDeployFromGitRepoRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.git; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformDeployFromDirectoryRequest; - -/** Data model for terraform deploy requests using scripts from a GIT Repo. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformDeployFromGitRepoRequest extends TerraformDeployFromDirectoryRequest { - - @NotNull - @Schema(description = "GIT Repo details from where the scripts can be fetched.") - private TerraformScriptGitRepoDetails gitRepoDetails; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformDestroyFromGitRepoRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformDestroyFromGitRepoRequest.java deleted file mode 100644 index 0193fbd4..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformDestroyFromGitRepoRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.git; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformDestroyFromDirectoryRequest; - -/** Data model for terraform destroy requests using scripts from a GIT Repo. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformDestroyFromGitRepoRequest extends TerraformDestroyFromDirectoryRequest { - - @Schema(description = "GIT Repo details from where the scripts can be fetched.") - private TerraformScriptGitRepoDetails gitRepoDetails; - - @NotNull - @Schema(description = "The .tfState file content after deployment") - private String tfState; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformModifyFromGitRepoRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformModifyFromGitRepoRequest.java deleted file mode 100644 index 229b2dee..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformModifyFromGitRepoRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.git; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformModifyFromDirectoryRequest; - -/** Data model for terraform modify requests using scripts from a GIT Repo. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformModifyFromGitRepoRequest extends TerraformModifyFromDirectoryRequest { - - @Schema(description = "GIT Repo details from where the scripts can be fetched.") - private TerraformScriptGitRepoDetails gitRepoDetails; - - @NotNull - @Schema(description = "The .tfState file content after deployment") - private String tfState; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformRequestWithScriptsGitRepo.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformRequestWithScriptsGitRepo.java new file mode 100644 index 00000000..cafca742 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformRequestWithScriptsGitRepo.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.models.request.git; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.eclipse.xpanse.terra.boot.models.request.TerraformRequest; + +/** The terraform request for executing command based on the scripts git repo details. */ +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(description = "Terraform async request with scripts git repo") +public class TerraformRequestWithScriptsGitRepo extends TerraformRequest implements Serializable { + + @Serial private static final long serialVersionUID = -9154036923917579783L; + + @NotNull + @Schema(description = "GIT Repo details from where the scripts can be fetched.") + private TerraformScriptsGitRepoDetails gitRepoDetails; +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformScriptGitRepoDetails.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformScriptsGitRepoDetails.java similarity index 80% rename from src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformScriptGitRepoDetails.java rename to src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformScriptsGitRepoDetails.java index 5db435a5..7a20d90b 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformScriptGitRepoDetails.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/git/TerraformScriptsGitRepoDetails.java @@ -7,11 +7,15 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import lombok.Data; /** Data model for defining the GIT repo information to fetch the scripts. */ @Data -public class TerraformScriptGitRepoDetails { +public class TerraformScriptsGitRepoDetails implements Serializable { + + @Serial private static final long serialVersionUID = -8723117896885097332L; @NotNull @Schema(description = "url of the GIT repo. This repo will be cloned.") diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncDestroyFromScriptsRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncDestroyFromScriptsRequest.java deleted file mode 100644 index d79a0c33..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncDestroyFromScriptsRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.scripts; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.webhook.WebhookConfig; - -/** Data model for the terraform async destroy requests. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformAsyncDestroyFromScriptsRequest extends TerraformDestroyWithScriptsRequest { - - @NotNull - @Schema(description = "Configuration information of webhook.") - private WebhookConfig webhookConfig; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncModifyFromScriptsRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncModifyFromScriptsRequest.java deleted file mode 100644 index 9d74ff8e..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncModifyFromScriptsRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.scripts; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.webhook.WebhookConfig; - -/** Data model for the terraform async modify requests. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformAsyncModifyFromScriptsRequest extends TerraformModifyWithScriptsRequest { - - @NotNull - @Schema(description = "Configuration information of webhook.") - private WebhookConfig webhookConfig; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncDeployFromScriptsRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncRequestWithScripts.java similarity index 57% rename from src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncDeployFromScriptsRequest.java rename to src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncRequestWithScripts.java index 1e9f9202..6b938612 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncDeployFromScriptsRequest.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformAsyncRequestWithScripts.java @@ -7,14 +7,20 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import lombok.Data; import lombok.EqualsAndHashCode; import org.eclipse.xpanse.terra.boot.models.request.webhook.WebhookConfig; -/** Data model for the terraform async deploy requests. */ +/** The terraform async request for executing command based on the scripts files. */ @EqualsAndHashCode(callSuper = true) @Data -public class TerraformAsyncDeployFromScriptsRequest extends TerraformDeployWithScriptsRequest { +@Schema(description = "Terraform async request with scripts files") +public class TerraformAsyncRequestWithScripts extends TerraformRequestWithScripts + implements Serializable { + + @Serial private static final long serialVersionUID = -751134396582688022L; @NotNull @Schema(description = "Configuration information of webhook.") diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformDestroyWithScriptsRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformDestroyWithScriptsRequest.java deleted file mode 100644 index 21f36806..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformDestroyWithScriptsRequest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.scripts; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.Map; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformDestroyFromDirectoryRequest; - -/** Terraform uses the request object destroy by the script. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformDestroyWithScriptsRequest extends TerraformDestroyFromDirectoryRequest { - - @NotNull - @NotEmpty - @Schema( - description = - "Map stores file name and content of all script files for destroy request.") - private Map scriptFiles; - - @NotNull - @Schema(description = "The .tfState file content after deployment") - private String tfState; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformModifyWithScriptsRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformModifyWithScriptsRequest.java deleted file mode 100644 index de6c52a5..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformModifyWithScriptsRequest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.models.request.scripts; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.Map; -import java.util.UUID; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformModifyFromDirectoryRequest; - -/** Terraform uses the request object modify by the script. */ -@EqualsAndHashCode(callSuper = true) -@Data -public class TerraformModifyWithScriptsRequest extends TerraformModifyFromDirectoryRequest { - - @Schema(description = "Id of the request.") - private UUID taskId; - - @NotNull - @NotEmpty - @Schema( - description = - "Map stores file name and content of all script files for modify request.") - private Map scriptFiles; - - @NotNull - @Schema(description = "The .tfState file content after deployment") - private String tfState; -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformDeployWithScriptsRequest.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformRequestWithScripts.java similarity index 59% rename from src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformDeployWithScriptsRequest.java rename to src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformRequestWithScripts.java index ac0aa2b2..2a11cd12 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformDeployWithScriptsRequest.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/scripts/TerraformRequestWithScripts.java @@ -8,15 +8,20 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import java.util.Map; import lombok.Data; import lombok.EqualsAndHashCode; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformDeployFromDirectoryRequest; +import org.eclipse.xpanse.terra.boot.models.request.TerraformRequest; -/** Terraform uses the request object deployed by the script. */ +/** The terraform request for executing command based on the scripts files. */ @EqualsAndHashCode(callSuper = true) @Data -public class TerraformDeployWithScriptsRequest extends TerraformDeployFromDirectoryRequest { +@Schema(description = "Terraform request with scripts files") +public class TerraformRequestWithScripts extends TerraformRequest implements Serializable { + + @Serial private static final long serialVersionUID = 7464467836284819109L; @NotNull @NotEmpty diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/webhook/WebhookConfig.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/webhook/WebhookConfig.java index 84205ba5..333c52eb 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/request/webhook/WebhookConfig.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/request/webhook/WebhookConfig.java @@ -7,12 +7,16 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import lombok.Data; import org.eclipse.xpanse.terra.boot.models.enums.AuthType; /** Configuration information class of webhook. */ @Data -public class WebhookConfig { +public class WebhookConfig implements Serializable { + + @Serial private static final long serialVersionUID = -4689930966905036460L; @NotNull @Schema(description = "Callback address after deployment/destroy is completed.") diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/response/ResultType.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/ResultType.java index 736d62c7..890a3ed5 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/response/ResultType.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/ResultType.java @@ -17,9 +17,11 @@ public enum ResultType { UNSUPPORTED_ENUM_VALUE("Unsupported Enum Value"), SERVICE_UNAVAILABLE("Service Unavailable"), UNAUTHORIZED("Unauthorized"), + INVALID_TERRAFORM_REQUEST("Invalid Terraform Request"), INVALID_GIT_REPO_DETAILS("Invalid Git Repo Details"), INVALID_TERRAFORM_TOOL("Invalid Terraform Tool"), INVALID_TERRAFORM_SCRIPTS("Invalid Terraform Scripts"), + SEND_AMQP_MESSAGE_FAILED("Send Amqp Message Failed"), RESULT_ALREADY_RETURNED_OR_REQUEST_ID_INVALID("Result Already Returned or RequestId Invalid"); private final String value; diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraBootSystemStatus.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraBootSystemStatus.java new file mode 100644 index 00000000..55a735a0 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraBootSystemStatus.java @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.models.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; +import java.util.UUID; +import lombok.Data; +import org.eclipse.xpanse.terra.boot.models.enums.HealthStatus; + +/** Describes health status of the system. */ +@Data +public class TerraBootSystemStatus implements Serializable { + + @Serial private static final long serialVersionUID = -6039352796356100544L; + + @NotNull + @Schema(description = "ID of the request.") + private UUID requestId; + + @NotNull + @Schema(description = "The health status of terra-boot service.") + private HealthStatus healthStatus; + + @NotBlank + @Schema(description = "The service type of terra-boot.") + private String serviceType; + + @NotBlank + @Schema(description = "The url of terra-boot service.") + private String serviceUrl; + + @Schema(description = "The error message of terra-boot service.") + private String errorMessage; +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraformPlan.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraformPlan.java new file mode 100644 index 00000000..42eed2c1 --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraformPlan.java @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.models.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Data model to represent terraform plan output. */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TerraformPlan implements Serializable { + + @Serial private static final long serialVersionUID = 4957985190544009199L; + + @NotNull + @Schema(description = "The id of the Terraform plan request") + private UUID requestId; + + @NotNull + @Schema(description = "Terraform plan as a JSON string") + private String plan; + + @Schema(description = "The version of the Terraform binary used to execute scripts.") + private String terraformVersionUsed; + + @Schema(description = "The error message of executing terraform plan command.") + private String errorMessage; +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraformResult.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraformResult.java index 18406d1b..6950e3b2 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraformResult.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/TerraformResult.java @@ -7,16 +7,25 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import java.util.Map; import java.util.UUID; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; /** Data model for the Terraform command execution results. */ @Data @Builder -public class TerraformResult { +@AllArgsConstructor +@NoArgsConstructor +public class TerraformResult implements Serializable { + @Serial private static final long serialVersionUID = 5138160212124102583L; + + @NotNull @Schema(description = "Id of the request") private UUID requestId; diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/validation/TerraformValidateDiagnostics.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/validation/TerraformValidateDiagnostics.java similarity index 88% rename from src/main/java/org/eclipse/xpanse/terra/boot/models/validation/TerraformValidateDiagnostics.java rename to src/main/java/org/eclipse/xpanse/terra/boot/models/response/validation/TerraformValidateDiagnostics.java index 3a012fe9..1562aab4 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/validation/TerraformValidateDiagnostics.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/validation/TerraformValidateDiagnostics.java @@ -3,7 +3,7 @@ * SPDX-FileCopyrightText: Huawei Inc. */ -package org.eclipse.xpanse.terra.boot.models.validation; +package org.eclipse.xpanse.terra.boot.models.response.validation; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/models/validation/TerraformValidationResult.java b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/validation/TerraformValidationResult.java similarity index 64% rename from src/main/java/org/eclipse/xpanse/terra/boot/models/validation/TerraformValidationResult.java rename to src/main/java/org/eclipse/xpanse/terra/boot/models/response/validation/TerraformValidationResult.java index b89ce47b..2d5b1925 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/models/validation/TerraformValidationResult.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/models/response/validation/TerraformValidationResult.java @@ -3,18 +3,27 @@ * SPDX-FileCopyrightText: Huawei Inc. */ -package org.eclipse.xpanse.terra.boot.models.validation; +package org.eclipse.xpanse.terra.boot.models.response.validation; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import java.util.List; +import java.util.UUID; import lombok.Data; /** Defines the Terraform validation result. */ @Data @JsonIgnoreProperties(ignoreUnknown = true) -public class TerraformValidationResult { +public class TerraformValidationResult implements Serializable { + + @Serial private static final long serialVersionUID = -4992253090497952906L; + + @NotNull + @Schema(description = "The request ID of the Terraform scripts validation.") + private UUID requestId; @NotNull @Schema(description = "Defines if the Terraform scripts is valid.") diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/security/oauth2/config/Oauth2WebSecurityConfig.java b/src/main/java/org/eclipse/xpanse/terra/boot/security/oauth2/config/Oauth2WebSecurityConfig.java index 8cd13e9f..7065c41a 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/security/oauth2/config/Oauth2WebSecurityConfig.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/security/oauth2/config/Oauth2WebSecurityConfig.java @@ -63,6 +63,9 @@ public class Oauth2WebSecurityConfig { @Value("${spring.security.oauth2.resourceserver.opaquetoken.client-secret}") private String clientSecret; + @Value("${springwolf.path.base:/springwolf}") + private String springwolfPathBase; + @Resource private Oauth2JwtDecoder oauth2JwtDecoder; /** @@ -81,6 +84,9 @@ public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { arc -> { arc.requestMatchers(AntPathRequestMatcher.antMatcher("/swagger-ui/**")) .permitAll(); + arc.requestMatchers( + AntPathRequestMatcher.antMatcher(springwolfPathBase + "/**")) + .permitAll(); arc.requestMatchers(AntPathRequestMatcher.antMatcher("/v3/**")).permitAll(); arc.requestMatchers(AntPathRequestMatcher.antMatcher("/error")).permitAll(); arc.anyRequest().authenticated(); diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformGitRepoService.java b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformGitRepoService.java deleted file mode 100644 index 1eb55446..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformGitRepoService.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.terraform.service; - -import jakarta.annotation.Resource; -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.xpanse.terra.boot.async.TaskConfiguration; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlan; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlanFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncDeployFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncDestroyFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncModifyFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformDeployFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformDestroyFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformModifyFromGitRepoRequest; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformScriptGitRepoDetails; -import org.eclipse.xpanse.terra.boot.models.response.TerraformResult; -import org.eclipse.xpanse.terra.boot.models.validation.TerraformValidationResult; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; - -/** Bean to manage all Terraform execution using scripts from a GIT Repo. */ -@Slf4j -@Component -public class TerraformGitRepoService { - - @Resource private RestTemplate restTemplate; - @Resource private TerraformScriptsHelper scriptsHelper; - @Resource private TerraformDirectoryService directoryService; - @Resource private TerraformResultPersistenceManage terraformResultPersistenceManage; - - /** Method of deployment a service using a script. */ - public TerraformValidationResult validateWithScripts( - TerraformDeployFromGitRepoRequest request) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(UUID.randomUUID().toString()); - scriptsHelper.prepareDeploymentFilesWithGitRepo( - taskWorkspace, request.getGitRepoDetails(), null); - String scriptsPath = - getScriptsLocationInTaskWorkspace(request.getGitRepoDetails(), taskWorkspace); - return directoryService.tfValidateFromDirectory(scriptsPath, request.getTerraformVersion()); - } - - /** Method to get terraform plan. */ - public TerraformPlan getTerraformPlanFromGitRepo( - TerraformPlanFromGitRepoRequest request, UUID uuid) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(uuid.toString()); - scriptsHelper.prepareDeploymentFilesWithGitRepo( - taskWorkspace, request.getGitRepoDetails(), null); - String scriptsPath = - getScriptsLocationInTaskWorkspace(request.getGitRepoDetails(), taskWorkspace); - return directoryService.getTerraformPlanFromDirectory(request, scriptsPath); - } - - /** Method of deployment a service using a script. */ - public TerraformResult deployFromGitRepo(TerraformDeployFromGitRepoRequest request, UUID uuid) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(uuid.toString()); - List scriptFiles = - scriptsHelper.prepareDeploymentFilesWithGitRepo( - taskWorkspace, request.getGitRepoDetails(), null); - String scriptsPath = - getScriptsLocationInTaskWorkspace(request.getGitRepoDetails(), taskWorkspace); - TerraformResult result = - directoryService.deployFromDirectory(request, scriptsPath, scriptFiles); - scriptsHelper.deleteTaskWorkspace(taskWorkspace); - return result; - } - - /** Method of modify a service using a script. */ - public TerraformResult modifyFromGitRepo(TerraformModifyFromGitRepoRequest request, UUID uuid) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(uuid.toString()); - List scriptFiles = - scriptsHelper.prepareDeploymentFilesWithGitRepo( - taskWorkspace, request.getGitRepoDetails(), request.getTfState()); - String scriptsPath = - getScriptsLocationInTaskWorkspace(request.getGitRepoDetails(), taskWorkspace); - TerraformResult result = - directoryService.modifyFromDirectory(request, scriptsPath, scriptFiles); - scriptsHelper.deleteTaskWorkspace(taskWorkspace); - return result; - } - - /** Method of destroy a service using a script. */ - public TerraformResult destroyFromGitRepo( - TerraformDestroyFromGitRepoRequest request, UUID uuid) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(uuid.toString()); - List scriptFiles = - scriptsHelper.prepareDeploymentFilesWithGitRepo( - taskWorkspace, request.getGitRepoDetails(), request.getTfState()); - String scriptsPath = - getScriptsLocationInTaskWorkspace(request.getGitRepoDetails(), taskWorkspace); - TerraformResult result = - directoryService.destroyFromDirectory(request, scriptsPath, scriptFiles); - scriptsHelper.deleteTaskWorkspace(taskWorkspace); - return result; - } - - /** Async deploy a source by terraform. */ - @Async(TaskConfiguration.TASK_EXECUTOR_NAME) - public void asyncDeployFromGitRepo( - TerraformAsyncDeployFromGitRepoRequest asyncDeployRequest, UUID uuid) { - TerraformResult result; - try { - result = deployFromGitRepo(asyncDeployRequest, uuid); - } catch (RuntimeException e) { - result = - TerraformResult.builder() - .commandStdOutput(null) - .commandStdError(e.getMessage()) - .isCommandSuccessful(false) - .terraformState(null) - .generatedFileContentMap(new HashMap<>()) - .build(); - } - result.setRequestId(asyncDeployRequest.getRequestId()); - String url = asyncDeployRequest.getWebhookConfig().getUrl(); - log.info("Deployment service complete, callback POST url:{}, requestBody:{}", url, result); - sendTerraformResult(url, result); - } - - /** Async modify a source by terraform. */ - @Async(TaskConfiguration.TASK_EXECUTOR_NAME) - public void asyncModifyFromGitRepo( - TerraformAsyncModifyFromGitRepoRequest asyncModifyRequest, UUID uuid) { - TerraformResult result; - try { - result = modifyFromGitRepo(asyncModifyRequest, uuid); - } catch (RuntimeException e) { - result = - TerraformResult.builder() - .commandStdOutput(null) - .commandStdError(e.getMessage()) - .isCommandSuccessful(false) - .terraformState(null) - .generatedFileContentMap(new HashMap<>()) - .build(); - } - result.setRequestId(asyncModifyRequest.getRequestId()); - String url = asyncModifyRequest.getWebhookConfig().getUrl(); - log.info("Modify service complete, callback POST url:{}, requestBody:{}", url, result); - sendTerraformResult(url, result); - } - - /** Async destroy resource of the service. */ - @Async(TaskConfiguration.TASK_EXECUTOR_NAME) - public void asyncDestroyFromGitRepo( - TerraformAsyncDestroyFromGitRepoRequest request, UUID uuid) { - TerraformResult result; - try { - result = destroyFromGitRepo(request, uuid); - } catch (RuntimeException e) { - result = - TerraformResult.builder() - .commandStdOutput(null) - .commandStdError(e.getMessage()) - .isCommandSuccessful(false) - .terraformState(null) - .generatedFileContentMap(new HashMap<>()) - .build(); - } - result.setRequestId(request.getRequestId()); - String url = request.getWebhookConfig().getUrl(); - log.info("Destroy service complete, callback POST url:{}, requestBody:{}", url, result); - sendTerraformResult(url, result); - } - - private void sendTerraformResult(String url, TerraformResult result) { - try { - restTemplate.postForLocation(url, result); - } catch (RestClientException e) { - log.error("error while sending terraform result", e); - terraformResultPersistenceManage.persistTerraformResult(result); - } - } - - private String getScriptsLocationInTaskWorkspace( - TerraformScriptGitRepoDetails terraformScriptGitRepoDetails, String taskWorkSpace) { - if (StringUtils.isNotBlank(terraformScriptGitRepoDetails.getScriptPath())) { - return taskWorkSpace + File.separator + terraformScriptGitRepoDetails.getScriptPath(); - } - return taskWorkSpace; - } -} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformRequestService.java b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformRequestService.java new file mode 100644 index 00000000..39c8aebc --- /dev/null +++ b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformRequestService.java @@ -0,0 +1,291 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +package org.eclipse.xpanse.terra.boot.terraform.service; + +import static org.eclipse.xpanse.terra.boot.logging.CustomRequestIdGenerator.REQUEST_ID; + +import jakarta.annotation.Resource; +import java.io.File; +import java.util.List; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.xpanse.terra.boot.models.enums.RequestType; +import org.eclipse.xpanse.terra.boot.models.exceptions.InvalidTerraformRequestException; +import org.eclipse.xpanse.terra.boot.models.exceptions.UnsupportedEnumValueException; +import org.eclipse.xpanse.terra.boot.models.request.TerraformRequest; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformAsyncRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformRequestWithScriptsGitRepo; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformScriptsGitRepoDetails; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncRequestWithScripts; +import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformRequestWithScripts; +import org.eclipse.xpanse.terra.boot.models.response.TerraBootSystemStatus; +import org.eclipse.xpanse.terra.boot.models.response.TerraformPlan; +import org.eclipse.xpanse.terra.boot.models.response.TerraformResult; +import org.eclipse.xpanse.terra.boot.models.response.validation.TerraformValidateDiagnostics; +import org.eclipse.xpanse.terra.boot.models.response.validation.TerraformValidationResult; +import org.slf4j.MDC; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +/** Terraform service classes are deployed form Directory. */ +@Slf4j +@Service +public class TerraformRequestService { + + @Resource private TerraformScriptsDirectoryService terraformScriptsDirectoryService; + @Resource private TerraformScriptsDirectoryHelper scriptsDirectoryHelper; + + /** Handle the request of health check. */ + public TerraBootSystemStatus healthCheck(UUID requestId) { + MDC.put(REQUEST_ID, requestId.toString()); + return terraformScriptsDirectoryService.tfHealthCheck(requestId); + } + + /** + * Handle terraform validate request and return result. + * + * @param request request. + * @return TerraformValidationResult. + */ + public TerraformValidationResult handleTerraformValidateRequest(TerraformRequest request) { + TerraformRequestWithScriptsDirectory requestWithDirectory = + convertRequestWithScriptsDirectory(request); + return terraformScriptsDirectoryService.tfValidateWithScriptsDirectory( + requestWithDirectory); + } + + /** + * Handle the plan request and return the TerraformPlan. + * + * @param request request. + * @return TerraformPlan. + */ + public TerraformPlan handleTerraformPlanRequest(TerraformRequest request) { + TerraformRequestWithScriptsDirectory requestWithDirectory = + convertRequestWithScriptsDirectory(request); + return terraformScriptsDirectoryService.getTerraformPlanWithScriptsDirectory( + requestWithDirectory); + } + + /** + * Handle the terraform request and return the TerraformResult. + * + * @param request request. + * @return TerraformResult. + */ + public TerraformResult handleTerraformDeploymentRequest(TerraformRequest request) { + TerraformRequestWithScriptsDirectory requestWithDirectory = + convertRequestWithScriptsDirectory(request); + switch (request.getRequestType()) { + case RequestType.DEPLOY -> { + return terraformScriptsDirectoryService.deployWithScriptsDirectory( + requestWithDirectory); + } + case RequestType.MODIFY -> { + return terraformScriptsDirectoryService.modifyWithScriptsDirectory( + requestWithDirectory); + } + case RequestType.DESTROY -> { + return terraformScriptsDirectoryService.destroyWithScriptsDirectory( + requestWithDirectory); + } + default -> + throw new UnsupportedEnumValueException( + String.format( + "RequestType value %s is not supported.", + request.getRequestType().toValue())); + } + } + + /** + * Process the async deployment request. + * + * @param request request. + */ + public void processAsyncDeploymentRequest(TerraformRequest request) { + TerraformAsyncRequestWithScriptsDirectory requestWithDirectory = + (TerraformAsyncRequestWithScriptsDirectory) + convertRequestWithScriptsDirectory(request); + switch (request.getRequestType()) { + case RequestType.DEPLOY -> + terraformScriptsDirectoryService.asyncDeployWithScriptsDirectory( + requestWithDirectory); + case RequestType.MODIFY -> + terraformScriptsDirectoryService.asyncModifyWithScriptsDirectory( + requestWithDirectory); + case RequestType.DESTROY -> + terraformScriptsDirectoryService.asyncDestroyWithScriptsDirectory( + requestWithDirectory); + default -> + throw new UnsupportedEnumValueException( + String.format( + "RequestType value %s is not supported.", + request.getRequestType().toValue())); + } + } + + private TerraformRequestWithScriptsDirectory convertRequestWithScriptsDirectory( + TerraformRequest request) { + validateTerraformRequest(request); + return switch (request) { + case TerraformRequestWithScriptsDirectory requestWithDirectory -> requestWithDirectory; + case TerraformRequestWithScriptsGitRepo requestWithScriptsGitRepo -> + convertRequestWithGitToDirectory(requestWithScriptsGitRepo); + case TerraformRequestWithScripts requestWithScripts -> + convertRequestWithScriptsToDirectory(requestWithScripts); + default -> + throw new UnsupportedEnumValueException( + String.format( + "RequestType value %s is not supported.", + request.getRequestType().toValue())); + }; + } + + /** + * Validate the terraform request. + * + * @param request request. + */ + private void validateTerraformRequest(TerraformRequest request) { + MDC.put(REQUEST_ID, request.getRequestId().toString()); + if (RequestType.DESTROY == request.getRequestType() + || RequestType.MODIFY == request.getRequestType()) { + if (StringUtils.isBlank(request.getTfState())) { + String errorMessage = + String.format( + "Terraform state is required for request with order type %s.", + request.getRequestType()); + log.error(errorMessage); + throw new InvalidTerraformRequestException(errorMessage); + } + } + if (request instanceof TerraformRequestWithScriptsDirectory requestWithDirectory) { + List scriptFiles = + scriptsDirectoryHelper.getDeploymentFilesFromTaskWorkspace( + requestWithDirectory.getScriptsDirectory()); + if (CollectionUtils.isEmpty(scriptFiles)) { + String errorMessage = + String.format( + "No Terraform scripts files found in the directory %s.", + requestWithDirectory.getScriptsDirectory()); + log.error(errorMessage); + throw new InvalidTerraformRequestException(errorMessage); + } + requestWithDirectory.setScriptFiles(scriptFiles); + } + } + + /** + * Get the error validate result. + * + * @param request request + * @param e exception + * @return TerraformValidationResult + */ + public TerraformValidationResult getErrorValidateResult(TerraformRequest request, Exception e) { + TerraformValidationResult result = new TerraformValidationResult(); + result.setRequestId(request.getRequestId()); + result.setValid(false); + result.setTerraformVersionUsed(request.getTerraformVersion()); + TerraformValidateDiagnostics diagnostics = new TerraformValidateDiagnostics(); + diagnostics.setDetail(e.getMessage()); + result.setDiagnostics(List.of(diagnostics)); + return result; + } + + /** + * Get the error plan result. + * + * @param request request + * @param e exception + * @return TerraformPlan + */ + public TerraformPlan getErrorPlanResult(TerraformRequest request, Exception e) { + return TerraformPlan.builder() + .requestId(request.getRequestId()) + .terraformVersionUsed(request.getTerraformVersion()) + .errorMessage(e.getMessage()) + .build(); + } + + /** + * Get the error deployment result. + * + * @param request request + * @param e exception + * @return TerraformResult + */ + public TerraformResult getErrorDeploymentResult(TerraformRequest request, Exception e) { + return TerraformResult.builder() + .requestId(request.getRequestId()) + .terraformVersionUsed(request.getTerraformVersion()) + .isCommandSuccessful(false) + .commandStdError(e.getMessage()) + .build(); + } + + /** + * Transform TerraformRequestWithScriptsGitRepo to TerraformRequestWithScriptsDirectory. + * + * @param request request with git repo. + * @return request with scripts directory. + */ + private TerraformRequestWithScriptsDirectory convertRequestWithGitToDirectory( + TerraformRequestWithScriptsGitRepo request) { + TerraformRequestWithScriptsDirectory requestWithDirectory = + new TerraformRequestWithScriptsDirectory(); + if (request instanceof TerraformAsyncRequestWithScriptsGitRepo) { + requestWithDirectory = new TerraformAsyncRequestWithScriptsDirectory(); + } + BeanUtils.copyProperties(request, requestWithDirectory); + String taskWorkspace = + scriptsDirectoryHelper.buildTaskWorkspace(request.getRequestId().toString()); + String scriptsPath = + getScriptsLocationInTaskWorkspace(request.getGitRepoDetails(), taskWorkspace); + requestWithDirectory.setScriptsDirectory(scriptsPath); + List scriptFiles = + scriptsDirectoryHelper.prepareDeploymentFilesWithGitRepo( + taskWorkspace, request.getGitRepoDetails(), request.getTfState()); + requestWithDirectory.setScriptFiles(scriptFiles); + return requestWithDirectory; + } + + private String getScriptsLocationInTaskWorkspace( + TerraformScriptsGitRepoDetails scriptsGitRepoDetails, String taskWorkSpace) { + if (StringUtils.isNotBlank(scriptsGitRepoDetails.getScriptPath())) { + return taskWorkSpace + File.separator + scriptsGitRepoDetails.getScriptPath(); + } + return taskWorkSpace; + } + + /** + * Transform TerraformRequestWithScripts to TerraformRequestWithScriptsDirectory. + * + * @param request request with scripts. + * @return request with scripts directory. + */ + private TerraformRequestWithScriptsDirectory convertRequestWithScriptsToDirectory( + TerraformRequestWithScripts request) { + TerraformRequestWithScriptsDirectory requestWithDirectory = + new TerraformRequestWithScriptsDirectory(); + if (request instanceof TerraformAsyncRequestWithScripts) { + requestWithDirectory = new TerraformAsyncRequestWithScriptsDirectory(); + } + BeanUtils.copyProperties(request, requestWithDirectory); + String scriptsPath = + scriptsDirectoryHelper.buildTaskWorkspace(request.getRequestId().toString()); + requestWithDirectory.setScriptsDirectory(scriptsPath); + List scriptFilesList = + scriptsDirectoryHelper.prepareDeploymentFilesWithScripts( + scriptsPath, request.getScriptFiles(), request.getTfState()); + requestWithDirectory.setScriptFiles(scriptFilesList); + return requestWithDirectory; + } +} diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformResultPersistenceManage.java b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformResultPersistenceManage.java index 062018e5..f9b74eef 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformResultPersistenceManage.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformResultPersistenceManage.java @@ -30,7 +30,7 @@ public class TerraformResultPersistenceManage { @Value("${failed.callback.response.store.location}") private String failedCallbackStoreLocation; - @Resource private TerraformScriptsHelper scriptsHelper; + @Resource private TerraformScriptsDirectoryHelper scriptsHelper; @Resource private TerraformResultSerializer terraformResultSerializer; /** diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsHelper.java b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsDirectoryHelper.java similarity index 96% rename from src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsHelper.java rename to src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsDirectoryHelper.java index 82dec38e..41e76e0c 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsHelper.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsDirectoryHelper.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -23,7 +24,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.xpanse.terra.boot.models.exceptions.TerraformExecutorException; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformScriptGitRepoDetails; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformScriptsGitRepoDetails; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -31,7 +32,7 @@ /** bean to host all generic methods shared from different types of Terraform deployers. */ @Slf4j @Component -public class TerraformScriptsHelper { +public class TerraformScriptsDirectoryHelper { public static final String TF_SCRIPT_FILE_EXTENSION = ".tf"; private static final String TF_STATE_FILE_NAME = "terraform.tfstate"; @@ -44,7 +45,7 @@ public class TerraformScriptsHelper { @Value("${clean.workspace.after.deployment.enabled:true}") private Boolean cleanWorkspaceAfterDeployment; - @Resource private ScriptsGitRepoManage scriptsGitRepoManage; + @Resource private TerraformScriptsGitRepoHelper terraformScriptsGitRepoHelper; /** * Create workspace for the Terraform deployment task. @@ -82,8 +83,7 @@ public File createTfStateFile(String taskWorkspace, String tfState) { throw new TerraformExecutorException("tfState file create error"); } File stateFile = new File(taskWorkspace, TF_STATE_FILE_NAME); - boolean overwrite = stateFile.exists(); - try (FileWriter scriptWriter = new FileWriter(stateFile, overwrite)) { + try (FileWriter scriptWriter = new FileWriter(stateFile, StandardCharsets.UTF_8, false)) { scriptWriter.write(tfState); log.info("tfState file create success, fileName: {}", stateFile.getAbsolutePath()); return stateFile; @@ -121,9 +121,9 @@ public List prepareDeploymentFilesWithScripts( * @return list of script files. */ public List prepareDeploymentFilesWithGitRepo( - String taskWorkspace, TerraformScriptGitRepoDetails gitRepoDetails, String tfState) { + String taskWorkspace, TerraformScriptsGitRepoDetails gitRepoDetails, String tfState) { List scriptFiles = - scriptsGitRepoManage.checkoutScripts(taskWorkspace, gitRepoDetails); + terraformScriptsGitRepoHelper.checkoutScripts(taskWorkspace, gitRepoDetails); List projectFiles = new ArrayList<>(scriptFiles); if (StringUtils.isNotBlank(tfState)) { File tfStateFile = createTfStateFile(taskWorkspace, tfState); @@ -156,8 +156,7 @@ private List buildScriptFiles(String taskWorkspace, Map sc private File createScriptFile(String taskWorkspace, String scriptName, String scriptContent) { File scriptFile = new File(taskWorkspace, scriptName); - boolean overwrite = scriptFile.exists(); - try (FileWriter scriptWriter = new FileWriter(scriptFile, overwrite)) { + try (FileWriter scriptWriter = new FileWriter(scriptFile, StandardCharsets.UTF_8, false)) { scriptWriter.write(scriptContent); log.info("Terraform script create success, fileName: {}", scriptFile.getAbsolutePath()); return scriptFile; diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformDirectoryService.java b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsDirectoryService.java similarity index 72% rename from src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformDirectoryService.java rename to src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsDirectoryService.java index 1df3c7a8..7471139c 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformDirectoryService.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsDirectoryService.java @@ -8,27 +8,20 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.Resource; -import java.io.File; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.eclipse.xpanse.terra.boot.async.TaskConfiguration; -import org.eclipse.xpanse.terra.boot.models.TerraBootSystemStatus; import org.eclipse.xpanse.terra.boot.models.enums.HealthStatus; import org.eclipse.xpanse.terra.boot.models.exceptions.InvalidTerraformToolException; import org.eclipse.xpanse.terra.boot.models.exceptions.TerraformExecutorException; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlan; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlanFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncDeployFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncDestroyFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncModifyFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformDeployFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformDestroyFromDirectoryRequest; -import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformModifyFromDirectoryRequest; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformAsyncRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.request.directory.TerraformRequestWithScriptsDirectory; +import org.eclipse.xpanse.terra.boot.models.response.TerraBootSystemStatus; +import org.eclipse.xpanse.terra.boot.models.response.TerraformPlan; import org.eclipse.xpanse.terra.boot.models.response.TerraformResult; -import org.eclipse.xpanse.terra.boot.models.validation.TerraformValidationResult; +import org.eclipse.xpanse.terra.boot.models.response.validation.TerraformValidationResult; import org.eclipse.xpanse.terra.boot.terraform.TerraformExecutor; import org.eclipse.xpanse.terra.boot.terraform.tool.TerraformInstaller; import org.eclipse.xpanse.terra.boot.terraform.tool.TerraformVersionsHelper; @@ -42,7 +35,8 @@ /** Terraform service classes are deployed form Directory. */ @Slf4j @Service -public class TerraformDirectoryService { +public class TerraformScriptsDirectoryService { + private static final String HELLO_WORLD_TF_NAME = "hello_world.tf"; private static final String HELLO_WORLD_TEMPLATE = """ @@ -54,7 +48,7 @@ public class TerraformDirectoryService { @Resource private TerraformInstaller installer; @Resource private RestTemplate restTemplate; @Resource private TerraformVersionsHelper versionHelper; - @Resource private TerraformScriptsHelper scriptsHelper; + @Resource private TerraformScriptsDirectoryHelper scriptsHelper; @Resource private TerraformResultPersistenceManage terraformResultPersistenceManage; /** @@ -62,13 +56,16 @@ public class TerraformDirectoryService { * * @return TerraBootSystemStatus. */ - public TerraBootSystemStatus tfHealthCheck() { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(UUID.randomUUID().toString()); + public TerraBootSystemStatus tfHealthCheck(UUID requestId) { + String taskWorkspace = scriptsHelper.buildTaskWorkspace(requestId.toString()); scriptsHelper.prepareDeploymentFilesWithScripts( taskWorkspace, Map.of(HELLO_WORLD_TF_NAME, HELLO_WORLD_TEMPLATE), null); + TerraformRequestWithScriptsDirectory request = new TerraformRequestWithScriptsDirectory(); + request.setScriptsDirectory(taskWorkspace); TerraformValidationResult terraformValidationResult = - tfValidateFromDirectory(taskWorkspace, null); + tfValidateWithScriptsDirectory(request); TerraBootSystemStatus systemStatus = new TerraBootSystemStatus(); + systemStatus.setRequestId(requestId); if (terraformValidationResult.isValid()) { systemStatus.setHealthStatus(HealthStatus.OK); return systemStatus; @@ -83,29 +80,30 @@ public TerraBootSystemStatus tfHealthCheck() { * * @return TfValidationResult. */ - public TerraformValidationResult tfValidateFromDirectory( - String taskWorkspace, String terraformVersion) { + public TerraformValidationResult tfValidateWithScriptsDirectory( + TerraformRequestWithScriptsDirectory request) { try { String executorPath = - installer.getExecutorPathThatMatchesRequiredVersion(terraformVersion); - SystemCmdResult result = executor.tfValidate(executorPath, taskWorkspace); + installer.getExecutorPathThatMatchesRequiredVersion( + request.getTerraformVersion()); + SystemCmdResult result = + executor.tfValidate(executorPath, request.getScriptsDirectory()); TerraformValidationResult validationResult = new ObjectMapper() .readValue( result.getCommandStdOutput(), TerraformValidationResult.class); + validationResult.setRequestId(request.getRequestId()); validationResult.setTerraformVersionUsed( versionHelper.getExactVersionOfExecutor(executorPath)); return validationResult; - } catch (JsonProcessingException ex) { - throw new IllegalStateException("Serialising string to object failed.", ex); + } catch (JsonProcessingException | InvalidTerraformToolException ex) { + throw new TerraformExecutorException("Failed get terraform validation result.", ex); } } /** Deploy a source by terraform. */ - public TerraformResult deployFromDirectory( - TerraformDeployFromDirectoryRequest request, - String taskWorkspace, - List scriptFiles) { + public TerraformResult deployWithScriptsDirectory( + TerraformRequestWithScriptsDirectory request) { SystemCmdResult result; String executorPath = null; try { @@ -118,14 +116,14 @@ public TerraformResult deployFromDirectory( executorPath, request.getVariables(), request.getEnvVariables(), - taskWorkspace); + request.getScriptsDirectory()); } else { result = executor.tfApply( executorPath, request.getVariables(), request.getEnvVariables(), - taskWorkspace); + request.getScriptsDirectory()); } } catch (InvalidTerraformToolException | TerraformExecutorException tfEx) { log.error("Terraform deploy service failed. error:{}", tfEx.getMessage()); @@ -133,19 +131,16 @@ public TerraformResult deployFromDirectory( result.setCommandSuccessful(false); result.setCommandStdError(tfEx.getMessage()); } - TerraformResult terraformResult = - transSystemCmdResultToTerraformResult(result, taskWorkspace, scriptFiles); + TerraformResult terraformResult = transSystemCmdResultToTerraformResult(result, request); terraformResult.setTerraformVersionUsed( versionHelper.getExactVersionOfExecutor(executorPath)); - scriptsHelper.deleteTaskWorkspace(taskWorkspace); + scriptsHelper.deleteTaskWorkspace(request.getScriptsDirectory()); return terraformResult; } /** Modify a source by terraform. */ - public TerraformResult modifyFromDirectory( - TerraformModifyFromDirectoryRequest request, - String taskWorkspace, - List scriptFiles) { + public TerraformResult modifyWithScriptsDirectory( + TerraformRequestWithScriptsDirectory request) { SystemCmdResult result; String executorPath = null; try { @@ -158,14 +153,14 @@ public TerraformResult modifyFromDirectory( executorPath, request.getVariables(), request.getEnvVariables(), - taskWorkspace); + request.getScriptsDirectory()); } else { result = executor.tfApply( executorPath, request.getVariables(), request.getEnvVariables(), - taskWorkspace); + request.getScriptsDirectory()); } } catch (InvalidTerraformToolException | TerraformExecutorException tfEx) { log.error("Terraform deploy service failed. error:{}", tfEx.getMessage()); @@ -173,20 +168,17 @@ public TerraformResult modifyFromDirectory( result.setCommandSuccessful(false); result.setCommandStdError(tfEx.getMessage()); } - TerraformResult terraformResult = - transSystemCmdResultToTerraformResult(result, taskWorkspace, scriptFiles); + TerraformResult terraformResult = transSystemCmdResultToTerraformResult(result, request); terraformResult.setTerraformVersionUsed( versionHelper.getExactVersionOfExecutor(executorPath)); - scriptsHelper.deleteTaskWorkspace(taskWorkspace); + scriptsHelper.deleteTaskWorkspace(request.getScriptsDirectory()); terraformResult.setRequestId(request.getRequestId()); return terraformResult; } /** Destroy resource of the service. */ - public TerraformResult destroyFromDirectory( - TerraformDestroyFromDirectoryRequest request, - String taskWorkspace, - List scriptFiles) { + public TerraformResult destroyWithScriptsDirectory( + TerraformRequestWithScriptsDirectory request) { SystemCmdResult result; String executorPath = null; try { @@ -198,25 +190,24 @@ public TerraformResult destroyFromDirectory( executorPath, request.getVariables(), request.getEnvVariables(), - taskWorkspace); + request.getScriptsDirectory()); } catch (InvalidTerraformToolException | TerraformExecutorException tfEx) { log.error("Terraform destroy service failed. error:{}", tfEx.getMessage()); result = new SystemCmdResult(); result.setCommandSuccessful(false); result.setCommandStdError(tfEx.getMessage()); } - TerraformResult terraformResult = - transSystemCmdResultToTerraformResult(result, taskWorkspace, scriptFiles); + TerraformResult terraformResult = transSystemCmdResultToTerraformResult(result, request); terraformResult.setTerraformVersionUsed( versionHelper.getExactVersionOfExecutor(executorPath)); - scriptsHelper.deleteTaskWorkspace(taskWorkspace); + scriptsHelper.deleteTaskWorkspace(request.getScriptsDirectory()); terraformResult.setRequestId(request.getRequestId()); return terraformResult; } /** Executes terraform plan command on a directory and returns the plan as a JSON string. */ - public TerraformPlan getTerraformPlanFromDirectory( - TerraformPlanFromDirectoryRequest request, String taskWorkspace) { + public TerraformPlan getTerraformPlanWithScriptsDirectory( + TerraformRequestWithScriptsDirectory request) { String executorPath = installer.getExecutorPathThatMatchesRequiredVersion(request.getTerraformVersion()); String result = @@ -224,9 +215,10 @@ public TerraformPlan getTerraformPlanFromDirectory( executorPath, request.getVariables(), request.getEnvVariables(), - taskWorkspace); - scriptsHelper.deleteTaskWorkspace(taskWorkspace); - TerraformPlan terraformPlan = TerraformPlan.builder().plan(result).build(); + request.getScriptsDirectory()); + scriptsHelper.deleteTaskWorkspace(request.getScriptsDirectory()); + TerraformPlan terraformPlan = + TerraformPlan.builder().plan(result).requestId(request.getRequestId()).build(); terraformPlan.setTerraformVersionUsed( versionHelper.getExactVersionOfExecutor(executorPath)); return terraformPlan; @@ -234,13 +226,11 @@ public TerraformPlan getTerraformPlanFromDirectory( /** Async deploy a source by terraform. */ @Async(TaskConfiguration.TASK_EXECUTOR_NAME) - public void asyncDeployWithScripts( - TerraformAsyncDeployFromDirectoryRequest asyncDeployRequest, - String taskWorkspace, - List scriptFiles) { + public void asyncDeployWithScriptsDirectory( + TerraformAsyncRequestWithScriptsDirectory asyncDeployRequest) { TerraformResult result; try { - result = deployFromDirectory(asyncDeployRequest, taskWorkspace, scriptFiles); + result = deployWithScriptsDirectory(asyncDeployRequest); } catch (RuntimeException e) { result = TerraformResult.builder() @@ -259,13 +249,11 @@ public void asyncDeployWithScripts( /** Async modify a source by terraform. */ @Async(TaskConfiguration.TASK_EXECUTOR_NAME) - public void asyncModifyWithScripts( - TerraformAsyncModifyFromDirectoryRequest asyncModifyRequest, - String taskWorkspace, - List scriptFiles) { + public void asyncModifyWithScriptsDirectory( + TerraformAsyncRequestWithScriptsDirectory asyncModifyRequest) { TerraformResult result; try { - result = modifyFromDirectory(asyncModifyRequest, taskWorkspace, scriptFiles); + result = modifyWithScriptsDirectory(asyncModifyRequest); } catch (RuntimeException e) { result = TerraformResult.builder() @@ -284,13 +272,11 @@ public void asyncModifyWithScripts( /** Async destroy resource of the service. */ @Async(TaskConfiguration.TASK_EXECUTOR_NAME) - public void asyncDestroyWithScripts( - TerraformAsyncDestroyFromDirectoryRequest request, - String taskWorkspace, - List scriptFiles) { + public void asyncDestroyWithScriptsDirectory( + TerraformAsyncRequestWithScriptsDirectory request) { TerraformResult result; try { - result = destroyFromDirectory(request, taskWorkspace, scriptFiles); + result = destroyWithScriptsDirectory(request); } catch (RuntimeException e) { result = TerraformResult.builder() @@ -317,14 +303,19 @@ private void sendTerraformResult(String url, TerraformResult result) { } private TerraformResult transSystemCmdResultToTerraformResult( - SystemCmdResult result, String taskWorkspace, List scriptFiles) { + SystemCmdResult result, TerraformRequestWithScriptsDirectory request) { TerraformResult terraformResult = - TerraformResult.builder().isCommandSuccessful(result.isCommandSuccessful()).build(); + TerraformResult.builder() + .isCommandSuccessful(result.isCommandSuccessful()) + .requestId(request.getRequestId()) + .build(); try { BeanUtils.copyProperties(result, terraformResult); - terraformResult.setTerraformState(scriptsHelper.getTerraformState(taskWorkspace)); + terraformResult.setTerraformState( + scriptsHelper.getTerraformState(request.getScriptsDirectory())); terraformResult.setGeneratedFileContentMap( - scriptsHelper.getDeploymentGeneratedFilesContent(taskWorkspace, scriptFiles)); + scriptsHelper.getDeploymentGeneratedFilesContent( + request.getScriptsDirectory(), request.getScriptFiles())); } catch (Exception e) { log.error("Failed to get terraform state and generated files content.", e); } diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/ScriptsGitRepoManage.java b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsGitRepoHelper.java similarity index 91% rename from src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/ScriptsGitRepoManage.java rename to src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsGitRepoHelper.java index 08e51e39..77dbed7a 100644 --- a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/ScriptsGitRepoManage.java +++ b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsGitRepoHelper.java @@ -5,7 +5,7 @@ package org.eclipse.xpanse.terra.boot.terraform.service; -import static org.eclipse.xpanse.terra.boot.terraform.service.TerraformScriptsHelper.TF_SCRIPT_FILE_EXTENSION; +import static org.eclipse.xpanse.terra.boot.terraform.service.TerraformScriptsDirectoryHelper.TF_SCRIPT_FILE_EXTENSION; import java.io.File; import java.util.ArrayList; @@ -20,7 +20,7 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.xpanse.terra.boot.models.exceptions.GitRepoCloneException; import org.eclipse.xpanse.terra.boot.models.exceptions.InvalidTerraformScriptsException; -import org.eclipse.xpanse.terra.boot.models.request.git.TerraformScriptGitRepoDetails; +import org.eclipse.xpanse.terra.boot.models.request.git.TerraformScriptsGitRepoDetails; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.retry.support.RetrySynchronizationManager; @@ -29,7 +29,7 @@ /** Bean to manage GIT clone. */ @Slf4j @Component -public class ScriptsGitRepoManage { +public class TerraformScriptsGitRepoHelper { /** * Method to check out scripts from a GIT repo. @@ -41,7 +41,8 @@ public class ScriptsGitRepoManage { retryFor = GitRepoCloneException.class, maxAttemptsExpression = "${spring.retry.max-attempts}", backoff = @Backoff(delayExpression = "${spring.retry.delay-millions}")) - public List checkoutScripts(String workspace, TerraformScriptGitRepoDetails scriptsRepo) { + public List checkoutScripts( + String workspace, TerraformScriptsGitRepoDetails scriptsRepo) { log.info( "Clone GIT repo to get the deployment scripts. Retry number: " + Objects.requireNonNull(RetrySynchronizationManager.getContext()) @@ -72,7 +73,8 @@ public List checkoutScripts(String workspace, TerraformScriptGitRepoDetail return files; } - private List getSourceFiles(String workspace, TerraformScriptGitRepoDetails scriptsRepo) { + private List getSourceFiles( + String workspace, TerraformScriptsGitRepoDetails scriptsRepo) { List sourceFiles = new ArrayList<>(); File directory = new File( @@ -96,7 +98,7 @@ private List getSourceFiles(String workspace, TerraformScriptGitRepoDetail } private void validateIfFolderContainsTerraformScripts( - List files, TerraformScriptGitRepoDetails scriptsRepo) { + List files, TerraformScriptsGitRepoDetails scriptsRepo) { boolean isScriptsExisted = files.stream().anyMatch(file -> file.getName().endsWith(TF_SCRIPT_FILE_EXTENSION)); if (!isScriptsExisted) { diff --git a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsService.java b/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsService.java deleted file mode 100644 index 44292b0b..00000000 --- a/src/main/java/org/eclipse/xpanse/terra/boot/terraform/service/TerraformScriptsService.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * SPDX-FileCopyrightText: Huawei Inc. - */ - -package org.eclipse.xpanse.terra.boot.terraform.service; - -import jakarta.annotation.Resource; -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.xpanse.terra.boot.async.TaskConfiguration; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlan; -import org.eclipse.xpanse.terra.boot.models.plan.TerraformPlanWithScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncDeployFromScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncDestroyFromScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformAsyncModifyFromScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformDeployWithScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformDestroyWithScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.request.scripts.TerraformModifyWithScriptsRequest; -import org.eclipse.xpanse.terra.boot.models.response.TerraformResult; -import org.eclipse.xpanse.terra.boot.models.validation.TerraformValidationResult; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; - -/** Terraform service classes are deployed form Scripts. */ -@Slf4j -@Service -public class TerraformScriptsService { - - @Resource private RestTemplate restTemplate; - @Resource private TerraformScriptsHelper scriptsHelper; - @Resource private TerraformDirectoryService directoryService; - @Resource private TerraformResultPersistenceManage terraformResultPersistenceManage; - - /** /** Method of deployment a service using a script. */ - public TerraformValidationResult validateWithScripts( - TerraformDeployWithScriptsRequest request) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(UUID.randomUUID().toString()); - scriptsHelper.prepareDeploymentFilesWithScripts( - taskWorkspace, request.getScriptFiles(), null); - return directoryService.tfValidateFromDirectory( - taskWorkspace, request.getTerraformVersion()); - } - - /** Method of deployment a service using a script. */ - public TerraformResult deployWithScripts(TerraformDeployWithScriptsRequest request, UUID uuid) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(uuid.toString()); - List files = - scriptsHelper.prepareDeploymentFilesWithScripts( - taskWorkspace, request.getScriptFiles(), null); - return directoryService.deployFromDirectory(request, taskWorkspace, files); - } - - /** Method of modify a service using a script. */ - public TerraformResult modifyWithScripts(TerraformModifyWithScriptsRequest request, UUID uuid) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(uuid.toString()); - List files = - scriptsHelper.prepareDeploymentFilesWithScripts( - taskWorkspace, request.getScriptFiles(), request.getTfState()); - return directoryService.modifyFromDirectory(request, taskWorkspace, files); - } - - /** Method of destroy a service using a script. */ - public TerraformResult destroyWithScripts( - TerraformDestroyWithScriptsRequest request, UUID uuid) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(uuid.toString()); - List files = - scriptsHelper.prepareDeploymentFilesWithScripts( - taskWorkspace, request.getScriptFiles(), request.getTfState()); - return directoryService.destroyFromDirectory(request, taskWorkspace, files); - } - - /** Method to get terraform plan. */ - public TerraformPlan getTerraformPlanFromScripts( - TerraformPlanWithScriptsRequest request, UUID uuid) { - String taskWorkspace = scriptsHelper.buildTaskWorkspace(uuid.toString()); - scriptsHelper.prepareDeploymentFilesWithScripts( - taskWorkspace, request.getScriptFiles(), null); - return directoryService.getTerraformPlanFromDirectory(request, uuid.toString()); - } - - /** Async deploy a source by terraform. */ - @Async(TaskConfiguration.TASK_EXECUTOR_NAME) - public void asyncDeployWithScripts( - TerraformAsyncDeployFromScriptsRequest asyncDeployRequest, UUID uuid) { - TerraformResult result; - try { - result = deployWithScripts(asyncDeployRequest, uuid); - } catch (RuntimeException e) { - result = - TerraformResult.builder() - .commandStdOutput(null) - .commandStdError(e.getMessage()) - .isCommandSuccessful(false) - .terraformState(null) - .generatedFileContentMap(new HashMap<>()) - .build(); - } - result.setRequestId(asyncDeployRequest.getRequestId()); - String url = asyncDeployRequest.getWebhookConfig().getUrl(); - log.info("Deployment service complete, callback POST url:{}, requestBody:{}", url, result); - sendTerraformResult(url, result); - } - - /** Async modify a source by terraform. */ - @Async(TaskConfiguration.TASK_EXECUTOR_NAME) - public void asyncModifyWithScripts( - TerraformAsyncModifyFromScriptsRequest asyncModifyRequest, UUID uuid) { - TerraformResult result; - try { - result = modifyWithScripts(asyncModifyRequest, uuid); - } catch (RuntimeException e) { - result = - TerraformResult.builder() - .commandStdOutput(null) - .commandStdError(e.getMessage()) - .isCommandSuccessful(false) - .terraformState(null) - .generatedFileContentMap(new HashMap<>()) - .build(); - } - result.setRequestId(asyncModifyRequest.getRequestId()); - String url = asyncModifyRequest.getWebhookConfig().getUrl(); - log.info("Modify service complete, callback POST url:{}, requestBody:{}", url, result); - sendTerraformResult(url, result); - } - - /** Async destroy resource of the service. */ - @Async(TaskConfiguration.TASK_EXECUTOR_NAME) - public void asyncDestroyWithScripts( - TerraformAsyncDestroyFromScriptsRequest request, UUID uuid) { - TerraformResult result; - try { - result = destroyWithScripts(request, uuid); - } catch (RuntimeException e) { - result = - TerraformResult.builder() - .commandStdOutput(null) - .commandStdError(e.getMessage()) - .isCommandSuccessful(false) - .terraformState(null) - .generatedFileContentMap(new HashMap<>()) - .build(); - } - result.setRequestId(request.getRequestId()); - String url = request.getWebhookConfig().getUrl(); - log.info("Destroy service complete, callback POST url:{}, requestBody:{}", url, result); - sendTerraformResult(url, result); - } - - private void sendTerraformResult(String url, TerraformResult result) { - try { - restTemplate.postForLocation(url, result); - } catch (RestClientException e) { - log.error("error while sending terraform result", e); - terraformResultPersistenceManage.persistTerraformResult(result); - } - } -} diff --git a/src/main/resources/application-amqp.properties b/src/main/resources/application-amqp.properties new file mode 100644 index 00000000..b6fc6bf9 --- /dev/null +++ b/src/main/resources/application-amqp.properties @@ -0,0 +1,30 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Huawei Inc. +# + +# amqp provider, default value: rabbitmq. +spring.amqp.provider=rabbitmq +# RabbitMQ Specific +spring.rabbitmq.host=localhost +spring.rabbitmq.port=5672 +spring.rabbitmq.username=xpanse +spring.rabbitmq.password=Xpanse@2023 +# Retry configuration for RabbitMQ listener +spring.rabbitmq.listener.simple.retry.max-attempts=3 +spring.rabbitmq.listener.simple.retry.initial-interval=3000 +spring.rabbitmq.listener.simple.retry.max-interval=3000 +# springwolf AsyncAPI +springwolf.enabled=true +# AsyncAPI Docket +springwolf.path.base=/queues +springwolf.path.docs=/docs +springwolf.docket.info.title=TerraBoot Async Request APIs +springwolf.docket.info.version=@project.version@ +springwolf.docket.info.description=TerraBoot Async Request APIs. +springwolf.docket.base-package=org.eclipse.xpanse.terra.boot.api.queues +springwolf.docket.servers.amqp-server.description=AMQP Server +springwolf.docket.servers.amqp-server.protocol=amqp +springwolf.docket.servers.amqp-server.host=${spring.rabbitmq.host}:${spring.rabbitmq.port} +# AMQP Plugin for springwolf AsyncAPI +springwolf.plugin.amqp.publishing.enabled=true \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ed17c385..3fe88182 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,6 +6,7 @@ spring.application.name=terra-boot server.port=9090 app.version=@project.version@ spring.banner.location=classpath:banner.txt +springwolf.enabled=false http.logging.enabled=true http.logging.exclude.uri=/v3/**,/swagger-ui/**,/favicon.ico,/h2-console/** log.terraform.stdout.stderr=true