From a9611bff21215eb5636a220cb39e3df5ceab7375 Mon Sep 17 00:00:00 2001 From: swaroopar Date: Mon, 14 Aug 2023 11:54:41 +0200 Subject: [PATCH] add environment variables to request body --- .../boot/api/TerraformApiController.java | 3 +- .../TerraformApiExceptionHandler.java | 37 +++++- .../request/TerraformDeployRequest.java | 11 +- .../request/TerraformDestroyRequest.java | 15 ++- .../boot/terraform/TerraformExecutor.java | 111 +++++++++++++----- 5 files changed, 136 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/eclipse/xpanse/terraform/boot/api/TerraformApiController.java b/src/main/java/org/eclipse/xpanse/terraform/boot/api/TerraformApiController.java index 9ef6ca4..de20a01 100644 --- a/src/main/java/org/eclipse/xpanse/terraform/boot/api/TerraformApiController.java +++ b/src/main/java/org/eclipse/xpanse/terraform/boot/api/TerraformApiController.java @@ -109,7 +109,6 @@ public TerraformResult destroy( description = "directory name where the Terraform module files exist.") @PathVariable("module_directory") String moduleDirectory, @Valid @RequestBody TerraformDestroyRequest terraformDestroyRequest) { - return this.terraformExecutor.destroy(terraformDestroyRequest.getVariables(), - moduleDirectory); + return this.terraformExecutor.destroy(terraformDestroyRequest, moduleDirectory); } } diff --git a/src/main/java/org/eclipse/xpanse/terraform/boot/api/exceptions/TerraformApiExceptionHandler.java b/src/main/java/org/eclipse/xpanse/terraform/boot/api/exceptions/TerraformApiExceptionHandler.java index f3f93c1..2f4e8a8 100644 --- a/src/main/java/org/eclipse/xpanse/terraform/boot/api/exceptions/TerraformApiExceptionHandler.java +++ b/src/main/java/org/eclipse/xpanse/terraform/boot/api/exceptions/TerraformApiExceptionHandler.java @@ -5,15 +5,19 @@ package org.eclipse.xpanse.terraform.boot.api.exceptions; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import lombok.extern.slf4j.Slf4j; import org.eclipse.xpanse.terraform.boot.models.exceptions.TerraformExecutorException; import org.eclipse.xpanse.terraform.boot.models.exceptions.UnsupportedEnumValueException; import org.eclipse.xpanse.terraform.boot.models.response.Response; import org.eclipse.xpanse.terraform.boot.models.response.ResultType; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageConversionException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; @@ -24,7 +28,6 @@ * Exception handler for exceptions thrown by the methods called by the API controller. */ @Slf4j -@Order(Ordered.HIGHEST_PRECEDENCE) @RestControllerAdvice public class TerraformApiExceptionHandler { @@ -63,4 +66,32 @@ public Response handleMethodArgumentTypeMismatchException( return Response.errorResponse(ResultType.UNPROCESSABLE_ENTITY, Collections.singletonList(ex.getMessage())); } + + /** + * Exception handler for MethodArgumentNotValidException. + */ + @ExceptionHandler({MethodArgumentNotValidException.class}) + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + @ResponseBody + public Response handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { + log.error("handleMethodArgumentNotValidException: ", ex); + BindingResult bindingResult = ex.getBindingResult(); + List errors = new ArrayList<>(); + for (FieldError fieldError : bindingResult.getFieldErrors()) { + errors.add(fieldError.getField() + ":" + fieldError.getDefaultMessage()); + } + return Response.errorResponse(ResultType.UNPROCESSABLE_ENTITY, errors); + } + + /** + * Exception handler for HttpMessageConversionException. + */ + @ExceptionHandler({HttpMessageConversionException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Response handleHttpMessageConversionException(HttpMessageConversionException ex) { + log.error("handleHttpMessageConversionException: ", ex); + String failMessage = ex.getMessage(); + return Response.errorResponse(ResultType.BAD_PARAMETERS, + Collections.singletonList(failMessage)); + } } diff --git a/src/main/java/org/eclipse/xpanse/terraform/boot/models/request/TerraformDeployRequest.java b/src/main/java/org/eclipse/xpanse/terraform/boot/models/request/TerraformDeployRequest.java index 21b80c7..d592e45 100644 --- a/src/main/java/org/eclipse/xpanse/terraform/boot/models/request/TerraformDeployRequest.java +++ b/src/main/java/org/eclipse/xpanse/terraform/boot/models/request/TerraformDeployRequest.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.util.HashMap; import java.util.Map; import lombok.Data; @@ -23,6 +24,12 @@ public class TerraformDeployRequest { @NotNull @Schema(description = "Key-value pairs of variables that must be used to execute the " - + "Terraform request.") - Map variables; + + "Terraform request.", + additionalProperties = Schema.AdditionalPropertiesValue.TRUE) + Map variables; + + @Schema(description = "Key-value pairs of variables that must be injected as environment " + + "variables to terraform process.", + additionalProperties = Schema.AdditionalPropertiesValue.TRUE) + Map envVariables = new HashMap<>(); } diff --git a/src/main/java/org/eclipse/xpanse/terraform/boot/models/request/TerraformDestroyRequest.java b/src/main/java/org/eclipse/xpanse/terraform/boot/models/request/TerraformDestroyRequest.java index 42b78c9..1662005 100644 --- a/src/main/java/org/eclipse/xpanse/terraform/boot/models/request/TerraformDestroyRequest.java +++ b/src/main/java/org/eclipse/xpanse/terraform/boot/models/request/TerraformDestroyRequest.java @@ -5,8 +5,11 @@ package org.eclipse.xpanse.terraform.boot.models.request; +import static io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue.TRUE; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.util.HashMap; import java.util.Map; import lombok.Data; @@ -17,7 +20,13 @@ public class TerraformDestroyRequest { @NotNull - @Schema(description = "Key-value pairs of variables that must be used to execute the " - + "Terraform request.") - Map variables; + @Schema(description = "Key-value pairs of regular variables that must be used to execute the " + + "Terraform request.", + additionalProperties = TRUE) + Map variables; + + @Schema(description = "Key-value pairs of variables that must be injected as environment " + + "variables to terraform process.", + additionalProperties = TRUE) + Map envVariables = new HashMap<>(); } diff --git a/src/main/java/org/eclipse/xpanse/terraform/boot/terraform/TerraformExecutor.java b/src/main/java/org/eclipse/xpanse/terraform/boot/terraform/TerraformExecutor.java index b43380f..cc7c54d 100644 --- a/src/main/java/org/eclipse/xpanse/terraform/boot/terraform/TerraformExecutor.java +++ b/src/main/java/org/eclipse/xpanse/terraform/boot/terraform/TerraformExecutor.java @@ -6,15 +6,21 @@ package org.eclipse.xpanse.terraform.boot.terraform; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Objects; +import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.eclipse.xpanse.terraform.boot.models.exceptions.TerraformExecutorException; import org.eclipse.xpanse.terraform.boot.models.request.TerraformDeployRequest; +import org.eclipse.xpanse.terraform.boot.models.request.TerraformDestroyRequest; import org.eclipse.xpanse.terraform.boot.models.response.TerraformResult; import org.eclipse.xpanse.terraform.boot.models.validation.TerraformValidationResult; import org.eclipse.xpanse.terraform.boot.terraform.utils.SystemCmd; @@ -29,6 +35,13 @@ @Slf4j @Component public class TerraformExecutor { + private static final String VARS_FILE_NAME = "variables.tfvars.json"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + static { + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + private final String moduleParentDirectoryPath; @@ -43,11 +56,11 @@ public class TerraformExecutor { /** * Constructor for the TerraformExecutor bean. * - * @param systemCmd SystemCmd bean - * @param moduleParentDirectoryPath value of `terraform.root.module.directory` property + * @param systemCmd SystemCmd bean + * @param moduleParentDirectoryPath value of `terraform.root.module.directory` property * @param isStdoutStdErrLoggingEnabled value of `log.terraform.stdout.stderr` property - * @param customTerraformBinary value of `terraform.binary.location` property - * @param terraformLogLevel value of `terraform.log.level` property + * @param customTerraformBinary value of `terraform.binary.location` property + * @param terraformLogLevel value of `terraform.log.level` property */ @Autowired public TerraformExecutor(SystemCmd systemCmd, @@ -78,7 +91,8 @@ public TerraformExecutor(SystemCmd systemCmd, * @return Returns result of SystemCmd executes. */ private SystemCmdResult tfInitCommand(String workspace) { - return execute(getTerraformCommand("init -no-color"), workspace); + return execute(getTerraformCommand("init -no-color"), + workspace, new HashMap<>()); } /** @@ -86,10 +100,11 @@ private SystemCmdResult tfInitCommand(String workspace) { * * @return Returns result of SystemCmd executes. */ - private SystemCmdResult tfPlanCommand(Map variables, String workspace) { + private SystemCmdResult tfPlanCommand(Map variables, + Map envVariables, String workspace) { return executeWithVariables( new StringBuilder(getTerraformCommand("plan -input=false -no-color ")), - variables, workspace); + variables, envVariables, workspace); } /** @@ -97,11 +112,12 @@ private SystemCmdResult tfPlanCommand(Map variables, String work * * @return Returns result of SystemCmd executes. */ - private SystemCmdResult tfApplyCommand(Map variables, String workspace) { + private SystemCmdResult tfApplyCommand(Map variables, + Map envVariables, String workspace) { return executeWithVariables( new StringBuilder( getTerraformCommand("apply -auto-approve -input=false -no-color ")), - variables, workspace); + variables, envVariables, workspace); } /** @@ -109,10 +125,11 @@ private SystemCmdResult tfApplyCommand(Map variables, String wor * * @return Returns result of SystemCmd executes. */ - private SystemCmdResult tfDestroyCommand(Map variables, String workspace) { + private SystemCmdResult tfDestroyCommand(Map variables, + Map envVariables, String workspace) { return executeWithVariables( new StringBuilder("terraform destroy -auto-approve -input=false -no-color "), - variables, workspace); + variables, envVariables, workspace); } /** @@ -121,17 +138,15 @@ private SystemCmdResult tfDestroyCommand(Map variables, String w * @return Returns result of SystemCmd executes. */ private SystemCmdResult executeWithVariables(StringBuilder command, - Map variables, String workspace) { - for (Map.Entry entry : variables.entrySet()) { - if (Objects.nonNull(entry.getKey()) && Objects.nonNull(entry.getValue())) { - command.append("-var=") - .append(entry.getKey()) - .append("=") - .append(entry.getValue()) - .append(" "); - } - } - return execute(command.toString(), workspace); + Map variables, + Map envVariables, + String workspace) { + createVariablesFile(variables, workspace); + command.append(" -var-file="); + command.append(VARS_FILE_NAME); + SystemCmdResult systemCmdResult = execute(command.toString(), workspace, envVariables); + cleanUpVariablesFile(workspace); + return systemCmdResult; } /** @@ -139,9 +154,11 @@ private SystemCmdResult executeWithVariables(StringBuilder command, * * @return SystemCmdResult */ - private SystemCmdResult execute(String cmd, String workspace) { + private SystemCmdResult execute(String cmd, String workspace, + @NonNull Map envVariables) { + envVariables.putAll(getTerraformLogConfig()); return this.systemCmd.execute(cmd, workspace, this.isStdoutStdErrLoggingEnabled, - getTerraformLogConfig()); + envVariables); } /** @@ -150,7 +167,8 @@ private SystemCmdResult execute(String cmd, String workspace) { public TerraformResult deploy(TerraformDeployRequest terraformDeployRequest, String workspace) { if (Boolean.TRUE.equals(terraformDeployRequest.getIsPlanOnly())) { SystemCmdResult tfPlanResult = - tfPlan(terraformDeployRequest.getVariables(), workspace); + tfPlan(terraformDeployRequest.getVariables(), + terraformDeployRequest.getEnvVariables(), workspace); return TerraformResult.builder() .commandStdOutput(tfPlanResult.getCommandStdOutput()) .commandStdError(tfPlanResult.getCommandStdError()) @@ -158,6 +176,7 @@ public TerraformResult deploy(TerraformDeployRequest terraformDeployRequest, Str .build(); } else { SystemCmdResult applyResult = tfApplyCommand(terraformDeployRequest.getVariables(), + terraformDeployRequest.getEnvVariables(), getModuleFullPath(workspace)); if (!applyResult.isCommandSuccessful()) { log.error("TFExecutor.tfApply failed."); @@ -175,10 +194,13 @@ public TerraformResult deploy(TerraformDeployRequest terraformDeployRequest, Str /** * Destroy resource of the service. */ - public TerraformResult destroy(Map variables, String workspace) { - tfPlan(variables, workspace); + public TerraformResult destroy(TerraformDestroyRequest terraformDestroyRequest, + String workspace) { + tfPlan(terraformDestroyRequest.getVariables(), terraformDestroyRequest.getEnvVariables(), + workspace); SystemCmdResult destroyResult = - tfDestroyCommand(variables, getModuleFullPath(workspace)); + tfDestroyCommand(terraformDestroyRequest.getVariables(), + terraformDestroyRequest.getEnvVariables(), getModuleFullPath(workspace)); if (!destroyResult.isCommandSuccessful()) { log.error("TFExecutor.tfDestroy failed."); throw new TerraformExecutorException("TFExecutor.tfDestroy failed.", @@ -199,7 +221,8 @@ public TerraformResult destroy(Map variables, String workspace) public TerraformValidationResult tfValidate(String moduleDirectory) { tfInit(moduleDirectory); SystemCmdResult systemCmdResult = - execute("terraform validate -json -no-color", getModuleFullPath(moduleDirectory)); + execute("terraform validate -json -no-color", getModuleFullPath(moduleDirectory), + new HashMap<>()); try { return new ObjectMapper().readValue(systemCmdResult.getCommandStdOutput(), TerraformValidationResult.class); @@ -212,9 +235,11 @@ private String getModuleFullPath(String moduleDirectory) { return this.moduleParentDirectoryPath + File.separator + moduleDirectory; } - private SystemCmdResult tfPlan(Map variables, String moduleDirectory) { + private SystemCmdResult tfPlan(Map variables, Map envVariables, + String moduleDirectory) { tfInit(moduleDirectory); - SystemCmdResult planResult = tfPlanCommand(variables, getModuleFullPath(moduleDirectory)); + SystemCmdResult planResult = + tfPlanCommand(variables, envVariables, getModuleFullPath(moduleDirectory)); if (!planResult.isCommandSuccessful()) { log.error("TFExecutor.tfPlan failed."); throw new TerraformExecutorException("TFExecutor.tfPlan failed.", @@ -245,4 +270,28 @@ private Map getTerraformLogConfig() { return Collections.singletonMap("TF_LOG", this.terraformLogLevel); } + private void createVariablesFile(Map variables, String workspace) { + try { + log.info("creating variables file"); + OBJECT_MAPPER.writeValue(new File(getVariablesFilePath(workspace)), variables); + } catch (IOException ioException) { + throw new TerraformExecutorException("Creating variables file failed", + ioException.getMessage()); + } + } + + private void cleanUpVariablesFile(String workspace) { + File file = new File(getVariablesFilePath(workspace)); + try { + log.info("cleaning up variables file"); + Files.deleteIfExists(file.toPath()); + } catch (IOException ioException) { + log.error("Cleanup of variables file failed", ioException); + } + } + + private String getVariablesFilePath(String workspace) { + return workspace + File.separator + VARS_FILE_NAME; + } + }