Skip to content

Commit

Permalink
Merge pull request #1 from swaroopar/feature/envVariables
Browse files Browse the repository at this point in the history
add environment variables to request body
  • Loading branch information
iskey authored Aug 14, 2023
2 parents 0881d22 + a9611bf commit 3b9dcb8
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -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<String> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<String, String> variables;
+ "Terraform request.",
additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
Map<String, Object> variables;

@Schema(description = "Key-value pairs of variables that must be injected as environment "
+ "variables to terraform process.",
additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
Map<String, String> envVariables = new HashMap<>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<String, String> variables;
@Schema(description = "Key-value pairs of regular variables that must be used to execute the "
+ "Terraform request.",
additionalProperties = TRUE)
Map<String, Object> variables;

@Schema(description = "Key-value pairs of variables that must be injected as environment "
+ "variables to terraform process.",
additionalProperties = TRUE)
Map<String, String> envVariables = new HashMap<>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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,
Expand Down Expand Up @@ -78,41 +91,45 @@ 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<>());
}

/**
* Executes terraform plan command.
*
* @return Returns result of SystemCmd executes.
*/
private SystemCmdResult tfPlanCommand(Map<String, String> variables, String workspace) {
private SystemCmdResult tfPlanCommand(Map<String, Object> variables,
Map<String, String> envVariables, String workspace) {
return executeWithVariables(
new StringBuilder(getTerraformCommand("plan -input=false -no-color ")),
variables, workspace);
variables, envVariables, workspace);
}

/**
* Executes terraform apply command.
*
* @return Returns result of SystemCmd executes.
*/
private SystemCmdResult tfApplyCommand(Map<String, String> variables, String workspace) {
private SystemCmdResult tfApplyCommand(Map<String, Object> variables,
Map<String, String> envVariables, String workspace) {
return executeWithVariables(
new StringBuilder(
getTerraformCommand("apply -auto-approve -input=false -no-color ")),
variables, workspace);
variables, envVariables, workspace);
}

/**
* Executes terraform destroy command.
*
* @return Returns result of SystemCmd executes.
*/
private SystemCmdResult tfDestroyCommand(Map<String, String> variables, String workspace) {
private SystemCmdResult tfDestroyCommand(Map<String, Object> variables,
Map<String, String> envVariables, String workspace) {
return executeWithVariables(
new StringBuilder("terraform destroy -auto-approve -input=false -no-color "),
variables, workspace);
variables, envVariables, workspace);
}

/**
Expand All @@ -121,27 +138,27 @@ private SystemCmdResult tfDestroyCommand(Map<String, String> variables, String w
* @return Returns result of SystemCmd executes.
*/
private SystemCmdResult executeWithVariables(StringBuilder command,
Map<String, String> variables, String workspace) {
for (Map.Entry<String, String> 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<String, Object> variables,
Map<String, String> 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;
}

/**
* Executes terraform commands.
*
* @return SystemCmdResult
*/
private SystemCmdResult execute(String cmd, String workspace) {
private SystemCmdResult execute(String cmd, String workspace,
@NonNull Map<String, String> envVariables) {
envVariables.putAll(getTerraformLogConfig());
return this.systemCmd.execute(cmd, workspace, this.isStdoutStdErrLoggingEnabled,
getTerraformLogConfig());
envVariables);
}

/**
Expand All @@ -150,14 +167,16 @@ 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())
.isCommandSuccessful(tfPlanResult.isCommandSuccessful())
.build();
} else {
SystemCmdResult applyResult = tfApplyCommand(terraformDeployRequest.getVariables(),
terraformDeployRequest.getEnvVariables(),
getModuleFullPath(workspace));
if (!applyResult.isCommandSuccessful()) {
log.error("TFExecutor.tfApply failed.");
Expand All @@ -175,10 +194,13 @@ public TerraformResult deploy(TerraformDeployRequest terraformDeployRequest, Str
/**
* Destroy resource of the service.
*/
public TerraformResult destroy(Map<String, String> 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.",
Expand All @@ -199,7 +221,8 @@ public TerraformResult destroy(Map<String, String> 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);
Expand All @@ -212,9 +235,11 @@ private String getModuleFullPath(String moduleDirectory) {
return this.moduleParentDirectoryPath + File.separator + moduleDirectory;
}

private SystemCmdResult tfPlan(Map<String, String> variables, String moduleDirectory) {
private SystemCmdResult tfPlan(Map<String, Object> variables, Map<String, String> 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.",
Expand Down Expand Up @@ -245,4 +270,28 @@ private Map<String, String> getTerraformLogConfig() {
return Collections.singletonMap("TF_LOG", this.terraformLogLevel);
}

private void createVariablesFile(Map<String, Object> 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;
}

}

0 comments on commit 3b9dcb8

Please sign in to comment.