Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add environment variables to request body #1

Merged
merged 1 commit into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}

}