Skip to content

Commit

Permalink
Addressing PR comments
Browse files Browse the repository at this point in the history
  • Loading branch information
gregmeldrum committed Nov 24, 2023
1 parent 8e90b88 commit 803d459
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@NoArgsConstructor
@AllArgsConstructor
public class CommandBundle {
private String executionType; // (serial / parallel)
private ExecutionType executionType;
private Boolean exitOnFailure;
private List<Command> commands;
}
1 change: 0 additions & 1 deletion service/terraform-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
<spring-boot-starter-web.version>3.1.5</spring-boot-starter-web.version>
<spring-boot-starter-test.version>3.1.5</spring-boot-starter-test.version>
<jupiter.version>5.3.1</jupiter.version>
<camel.version>4.0.0-RC2</camel.version>
<commons-collections4.version>4.4</commons-collections4.version>
<snakeyaml.version>2.0</snakeyaml.version>
<maven-shade-plugin.version>3.3.0</maven-shade-plugin.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private CompletableFuture<Boolean> run(String... commands) throws IOException {
private CompletableFuture<Boolean> run(Map<String, String> envVars, String... commands) throws IOException {
assert commands.length > 0;
ProcessLauncher[] launchers = new ProcessLauncher[commands.length];
for (int i = 0; i < commands.length; i++) {
for (int i = 0; i < launchers.length; i++) {
launchers[i] = getTerraformLauncher(commands[i], envVars);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void saveLogToFile(CommandRequest request, List<String> logs) throws IOEx

public CommandResult buildTfCommandResult(List<String> jsonLogs) {
if (CollectionUtils.isEmpty(jsonLogs)) {
throw new IllegalArgumentException("Cannot extract information empty logs");
throw new IllegalArgumentException("No terraform logs were collected. Unable to process response.");
}

List<Map<String, Object>> successLogs = jsonLogs.stream()
Expand Down Expand Up @@ -128,11 +128,11 @@ private String extractDiagnosticDetailMessage(Object diagnostic) {
if (diagnostic == null) {
return "";
}
if (!(diagnostic instanceof Map)) {
throw new IllegalArgumentException("Unsupported object type encountered");
if (diagnostic instanceof Map diagnosticMap) {
return String.valueOf(diagnosticMap.get("detail"));
}
return String.valueOf(((Map<String, Object>) diagnostic).get("detail"));

throw new IllegalArgumentException("Unsupported terraform log object type encountered");
}

private String extractResourceAddressFromDiagnostic(Object diagnostic) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import static com.solace.maas.ep.event.management.agent.plugin.constants.RouteConstants.COMMAND_CORRELATION_ID;
import static com.solace.maas.ep.event.management.agent.plugin.constants.RouteConstants.MESSAGING_SERVICE_ID;
Expand Down Expand Up @@ -54,55 +55,79 @@ public void execute(CommandRequest request, Command command, Map<String, String>
try (TerraformClient terraformClient = terraformClientFactory.createClient()) {

Path configPath = createConfigPath(request);
terraformClient.setWorkingDirectory(configPath.toFile());
List<String> output = new ArrayList<>();

// Write each terraform to the output list so that it can be processed later
// Also write the output to the main log to be streamed back to EP
terraformClient.setOutputListener(tfLog -> {
MDC.put("traceId", traceId);
MDC.put("spanId", spanId);
output.add(tfLog);
log.debug("Terraform output: {}", tfLog);
});
String commandVerb = command.getCommand();

switch (commandVerb) {
case "apply" -> {
writeHclToFile(command, configPath);
terraformClient.plan(envVars).get();
terraformClient.apply(envVars).get();
}
case "write_HCL" -> writeHclToFile(command, configPath);
default -> log.error("Cannot handle arbitrary commands.");
List<String> logOutput = setupTerraformClient(terraformClient, configPath, traceId, spanId);
String commandVerb = executeTerraformCommand(command, envVars, configPath, terraformClient);
processTerraformResponse(request, command, commandVerb, logOutput);
} catch (InterruptedException e) {
log.error("Received a thread interrupt while executing the terraform command", e);
Thread.currentThread().interrupt();
} catch (Exception e) {
log.error("An error was encountered while executing the terraform command", e);
setCommandError(command, e);
}
}

private static List<String> setupTerraformClient(TerraformClient terraformClient, Path configPath, String traceId, String spanId) {
terraformClient.setWorkingDirectory(configPath.toFile());
List<String> output = new ArrayList<>();

// Write each terraform to the output list so that it can be processed later
// Also write the output to the main log to be streamed back to EP
terraformClient.setOutputListener(tfLog -> {
MDC.put("traceId", traceId);
MDC.put("spanId", spanId);
output.add(tfLog);
log.debug("Terraform output: {}", tfLog);
});
return output;
}

private static String executeTerraformCommand(Command command, Map<String, String> envVars, Path configPath, TerraformClient terraformClient) throws IOException, InterruptedException, ExecutionException {
String commandVerb = command.getCommand();
switch (commandVerb) {
case "apply" -> {
writeHclToFile(command, configPath);
terraformClient.plan(envVars).get();
terraformClient.apply(envVars).get();
}
case "write_HCL" -> writeHclToFile(command, configPath);
default -> throw new IllegalArgumentException("Unsupported command " + commandVerb);
}
return commandVerb;
}

// Process logs and create the result
if (Boolean.TRUE.equals(command.getIgnoreResult())) {
private void processTerraformResponse(CommandRequest request, Command command, String commandVerb, List<String> output) throws IOException {
// Process logs and create the result
if (Boolean.TRUE.equals(command.getIgnoreResult())) {
command.setResult(CommandResult.builder()
.status(JobStatus.success)
.logs(List.of())
.errors(List.of())
.build());
} else {
if (!"write_HCL".equals(commandVerb)) {
terraformLogProcessingService.saveLogToFile(request, output);
command.setResult(terraformLogProcessingService.buildTfCommandResult(output));
} else {
command.setResult(CommandResult.builder()
.status(JobStatus.success)
.logs(List.of())
.errors(List.of())
.build());
} else {
if (!"write_HCL".equals(commandVerb)) {
terraformLogProcessingService.saveLogToFile(request, output);
command.setResult(terraformLogProcessingService.buildTfCommandResult(output));
} else {
command.setResult(CommandResult.builder()
.status(JobStatus.success)
.logs(List.of())
.errors(List.of())
.build());
}
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}

private void setCommandError(Command command, Exception e) {
command.setResult(CommandResult.builder()
.status(JobStatus.error)
.logs(List.of())
.errors(List.of(
Map.of("message", e.getMessage(),
"errorType", e.getClass().getName())))
.build());
}

private Path createConfigPath(CommandRequest request) {
Path configPath = Paths.get(terraformProperties.getWorkingDirectoryRoot() + File.separator
+ request.getContext()
Expand All @@ -123,8 +148,18 @@ private Path createConfigPath(CommandRequest request) {

private static void writeHclToFile(Command command, Path configPath) throws IOException {
if (StringUtils.isNotEmpty(command.getBody())) {
byte[] decodedBytes = Base64.getDecoder().decode(command.getBody());
Files.write(configPath.resolve(TF_CONFIG_FILENAME), decodedBytes);
// At the moment, we only support base64 decoding
Map<String, String> parameters = command.getParameters();
if (parameters != null && parameters.containsKey("Content-Encoding") && "base64".equals(parameters.get("Content-Encoding"))) {
byte[] decodedBytes = Base64.getDecoder().decode(command.getBody());
Files.write(configPath.resolve(TF_CONFIG_FILENAME), decodedBytes);
} else {
if (parameters == null || !parameters.containsKey("Content-Encoding")) {
throw new IllegalArgumentException("Missing Content-Encoding property in command parameters.");
}

throw new IllegalArgumentException("Unsupported encoding type " + parameters.get("Content-Encoding"));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.solace.maas.ep.event.management.agent.plugin.command.model.CommandRequest;
import com.solace.maas.ep.event.management.agent.plugin.command.model.CommandResult;
import com.solace.maas.ep.event.management.agent.plugin.command.model.CommandType;
import com.solace.maas.ep.event.management.agent.plugin.command.model.ExecutionType;
import com.solace.maas.ep.event.management.agent.plugin.command.model.JobStatus;
import com.solace.maas.ep.event.management.agent.plugin.terraform.client.TerraformClient;
import com.solace.maas.ep.event.management.agent.plugin.terraform.configuration.TerraformProperties;
Expand Down Expand Up @@ -115,7 +116,7 @@ public void testCreateResourceHappyPath() throws IOException {
}

@Test
public void testCreateResourceFailurePath() throws IOException {
public void testCreateResourceTerraformErrorFailurePath() throws IOException {

String newQueueTf = getResourceAsString(resourceLoader.getResource("classpath:tfFiles/newQueue.tf"));
List<String> newQueueTfLogs = getResourceAsStringArray(resourceLoader.getResource("classpath:tfLogs/tfAddErrorSubscriptionAlreadyPresent.txt"));
Expand Down Expand Up @@ -146,6 +147,83 @@ public void testCreateResourceFailurePath() throws IOException {
}
}

@Test
public void testCreateResourceMissingParameterFailurePath() {

String newQueueTf = getResourceAsString(resourceLoader.getResource("classpath:tfFiles/newQueue.tf"));

// Generate a command without the expected base64 parameters
Command command = generateCommand("apply", newQueueTf, false, Map.of());
CommandRequest terraformRequest = generateCommandRequest(command);

terraformManager.execute(terraformRequest, command, Map.of());

// Validate that the plan and apply apis are called
for (CommandBundle commandBundle : terraformRequest.getCommandBundles()) {
for (Command tfCommand : commandBundle.getCommands()) {

CommandResult result = tfCommand.getResult();
assertEquals(JobStatus.error, result.getStatus());
assertTrue(result.getErrors().get(0).get("errorType").toString().contains("java.lang.IllegalArgumentException"));
assertTrue(result.getErrors().get(0).get("message").toString().contains("Missing Content-Encoding property in command parameters."));
}
}
}

@Test
public void testCreateResourceNoLogsFailurePath() throws IOException {

String newQueueTf = getResourceAsString(resourceLoader.getResource("classpath:tfFiles/newQueue.tf"));

Command command = generateCommand("apply", newQueueTf);
CommandRequest terraformRequest = generateCommandRequest(command);

when(terraformClient.plan(Map.of())).thenReturn(CompletableFuture.supplyAsync(() -> true));
when(terraformClient.apply(Map.of())).thenReturn(CompletableFuture.supplyAsync(() -> true));

// Setup the empty output
setupLogMock(List.of());

terraformManager.execute(terraformRequest, command, Map.of());

// Validate that the plan api is called
verify(terraformClient, times(1)).plan(any());
verify(terraformClient, times(1)).apply(any());

// Validate that the plan and apply apis are called
for (CommandBundle commandBundle : terraformRequest.getCommandBundles()) {
for (Command tfCommand : commandBundle.getCommands()) {

CommandResult result = tfCommand.getResult();
assertEquals(JobStatus.error, result.getStatus());
assertTrue(result.getErrors().get(0).get("errorType").toString().contains("java.lang.IllegalArgumentException"));
assertTrue(result.getErrors().get(0).get("message").toString().contains("No terraform logs were collected. Unable to process response."));
}
}
}

@Test
public void testCreateResourceUnknownCommandFailurePath() {

String newQueueTf = getResourceAsString(resourceLoader.getResource("classpath:tfFiles/newQueue.tf"));

Command command = generateCommand("appply", newQueueTf);
CommandRequest terraformRequest = generateCommandRequest(command);

terraformManager.execute(terraformRequest, command, Map.of());

// Validate that the plan and apply apis are called
for (CommandBundle commandBundle : terraformRequest.getCommandBundles()) {
for (Command tfCommand : commandBundle.getCommands()) {

CommandResult result = tfCommand.getResult();
assertEquals(JobStatus.error, result.getStatus());
assertTrue(result.getErrors().get(0).get("errorType").toString().contains("java.lang.IllegalArgumentException"));
assertTrue(result.getErrors().get(0).get("message").toString().contains("Unsupported command appply"));
}
}
}

@Test
public void testIgnoreResult() throws IOException {

Expand Down Expand Up @@ -217,21 +295,29 @@ private static Command generateCommand(String tfCommand, String body) {
}

private static Command generateCommand(String tfCommand, String body, Boolean ignoreResult) {
return generateCommand(tfCommand, body, ignoreResult,
Map.of("Content-Type", "application/hcl",
"Content-Encoding", "base64"));
}

private static Command generateCommand(String tfCommand, String body, Boolean ignoreResult, Map<String, String> parameters) {
return Command.builder()
.body(Optional.ofNullable(body)
.map(b -> Base64.getEncoder().encodeToString(b.getBytes(UTF_8)))
.orElse(""))
.command(tfCommand)
.commandType(CommandType.terraform)
.ignoreResult(ignoreResult)
.parameters(parameters)
.build();
}


private static CommandRequest generateCommandRequest(Command commandRequest) {
return CommandRequest.builder()
.commandBundles(List.of(
CommandBundle.builder()
.executionType("serial")
.executionType(ExecutionType.serial)
.exitOnFailure(false)
.commands(List.of(commandRequest))
.build()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ private List<CommandBundle> executeTerraformCommand(String hclFileName, String t
Command commandRequest = Command.builder()
.body(Base64.getEncoder().encodeToString(terraformString.getBytes(UTF_8)))
.command(tfVerb)
.parameters(Map.of(
"Content-Type", "application/hcl",
"Content-Encoding", "base64"))
.build();
CommandRequest terraformRequest = CommandRequest.builder()
.commandBundles(List.of(CommandBundle.builder()
Expand Down

0 comments on commit 803d459

Please sign in to comment.