Skip to content

Commit

Permalink
Terrakube Configuration Language (#79)
Browse files Browse the repository at this point in the history
Terrakube Configuration Language v1
  • Loading branch information
alfespa17 authored Oct 24, 2021
1 parent 1cb0f60 commit b903cbd
Show file tree
Hide file tree
Showing 35 changed files with 791 additions and 207 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/maven-settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</repository>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/azbuilder/azb-api-client-spring-boot-starter</url>
<url>https://maven.pkg.github.com/azbuilder/terrakube-spring-boot-starter</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
Expand Down
30 changes: 11 additions & 19 deletions api-job/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@
<groupId>org.azbuilder.api.job</groupId>
<artifactId>api-job</artifactId>
<version>${revision}</version>
<packaging>jar</packaging>
<name>Terrakube Schedule Job</name>
<description>Spring Boot Terrakube schedule job</description>
<properties>
<revision>1.5.0-beta.1</revision>
<java.version>11</java.version>
<okhttp.version>4.9.1</okhttp.version>
<api-client-starter.version>0.6.0</api-client-starter.version>
<api-client-starter.version>0.7.0-beta.1</api-client-starter.version>
<terraform-spring-boot-starter.version>0.2.1</terraform-spring-boot-starter.version>
<lombok.version>1.18.20</lombok.version>
<maven-dependency-plugin.version>3.2.0</maven-dependency-plugin.version>
</properties>
<dependencies>
<dependency>
Expand All @@ -29,6 +32,12 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.azbuilder.terraform</groupId>
Expand All @@ -45,23 +54,6 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.azbuilder.api.job;
package org.azbuilder.api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,64 +1,55 @@
package org.azbuilder.api.job.schedule;
package org.azbuilder.api.schedule;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.azbuilder.api.client.RestClient;
import org.azbuilder.api.client.TerrakubeClient;
import org.azbuilder.api.client.model.organization.Organization;
import org.azbuilder.api.client.model.organization.job.Job;
import org.azbuilder.api.client.model.organization.job.JobRequest;
import org.azbuilder.api.client.model.organization.vcs.Vcs;
import org.azbuilder.api.client.model.organization.workspace.Workspace;
import org.azbuilder.api.client.model.organization.workspace.variable.Variable;
import org.azbuilder.api.client.model.response.ResponseWithInclude;
import org.azbuilder.terraform.TerraformCommand;
import org.azbuilder.api.schedule.executor.ExecutorJob;
import org.azbuilder.api.schedule.yaml.Flow;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@Service
@Slf4j
public class Pending {
@Service
public class JobService {

@Autowired
RestClient restClient;
TerrakubeClient terrakubeClient;

@Value("${org.azbuilder.executor.url}")
private String executorUrl;

@Scheduled(fixedRate = 60000)
public void pendingJobs() {
for (Job job : searchPendingJobs()) {
TerraformJob terraformJob = processPendingJob(job);
updateJobStatus(job, terraformJob);
}
}

private TerraformJob processPendingJob(Job job) {
public boolean execute(Job job, String stepId, Flow flow) {
log.info("Pending Job: {} WorkspaceId: {}", job.getId(), job.getRelationships().getWorkspace().getData().getId());

TerraformJob terraformJob = new TerraformJob();
terraformJob.setOrganizationId(job.getRelationships().getOrganization().getData().getId());
terraformJob.setWorkspaceId(job.getRelationships().getWorkspace().getData().getId());
terraformJob.setJobId(job.getId());
ExecutorJob executorJob = new ExecutorJob();
executorJob.setOrganizationId(job.getRelationships().getOrganization().getData().getId());
executorJob.setWorkspaceId(job.getRelationships().getWorkspace().getData().getId());
executorJob.setJobId(job.getId());
executorJob.setStepId(stepId);

log.info("Checking Variables");
ResponseWithInclude<Workspace, Variable> workspaceData = restClient.getWorkspaceByIdWithVariables(terraformJob.getOrganizationId(), terraformJob.getWorkspaceId());
ResponseWithInclude<Workspace, Variable> workspaceData = terrakubeClient.getWorkspaceByIdWithVariables(executorJob.getOrganizationId(), executorJob.getWorkspaceId());
if (workspaceData.getData().getRelationships().getVcs().getData() != null) {
Vcs vcs = restClient.getVcsById(job.getRelationships().getOrganization().getData().getId(), workspaceData.getData().getRelationships().getVcs().getData().getId()).getData();
terraformJob.setVcsType(vcs.getAttributes().getVcsType());
terraformJob.setAccessToken(vcs.getAttributes().getAccessToken());
log.info("Private Repository {}",terraformJob.getVcsType());
Vcs vcs = terrakubeClient.getVcsById(job.getRelationships().getOrganization().getData().getId(), workspaceData.getData().getRelationships().getVcs().getData().getId()).getData();
executorJob.setVcsType(vcs.getAttributes().getVcsType());
executorJob.setAccessToken(vcs.getAttributes().getAccessToken());
log.info("Private Repository {}", executorJob.getVcsType());
} else {
terraformJob.setVcsType("PUBLIC");
executorJob.setVcsType("PUBLIC");
log.info("Public Repository");
}

Expand All @@ -76,56 +67,49 @@ private TerraformJob processPendingJob(Job job) {
}
log.info("Variable Key: {} Value {}", variable.getAttributes().getKey(), variable.getAttributes().isSensitive() ? "sensitive" : variable.getAttributes().getValue());
}
terraformJob.setVariables(variables);
terraformJob.setEnvironmentVariables(environmentVariables);

terraformJob.setTerraformCommand(TerraformCommand.valueOf(job.getAttributes().getCommand()));
terraformJob.setTerraformVersion(workspaceData.getData().getAttributes().getTerraformVersion());
terraformJob.setSource(workspaceData.getData().getAttributes().getSource());
terraformJob.setBranch(workspaceData.getData().getAttributes().getBranch());
executorJob.setVariables(variables);
executorJob.setEnvironmentVariables(environmentVariables);

executorJob.setCommandList(flow.getCommands());
executorJob.setType(flow.getType());
executorJob.setTerraformVersion(workspaceData.getData().getAttributes().getTerraformVersion());
executorJob.setSource(workspaceData.getData().getAttributes().getSource());
executorJob.setBranch(workspaceData.getData().getAttributes().getBranch());

return terraformJob;
return sendToExecutor(job, executorJob);
}

private List<Job> searchPendingJobs() {
ResponseWithInclude<List<Organization>, Job> organizationJobList = restClient.getAllOrganizationsWithJobStatus("pending");
public List<Job> searchPendingJobs() {
ResponseWithInclude<List<Organization>, Job> organizationJobList = terrakubeClient.getAllOrganizationsWithJobStatus("pending");

if (organizationJobList.getData().size() > 0 && organizationJobList.getIncluded() != null)
if (!organizationJobList.getData().isEmpty() && organizationJobList.getIncluded() != null)
return organizationJobList.getIncluded();
else
return new ArrayList<>();
}

private void updateJobStatus(Job job, TerraformJob terraformJob) {
public void completeJob(Job job) {
JobRequest jobRequest = new JobRequest();
job.getAttributes().setStatus("completed");
jobRequest.setData(job);
terrakubeClient.updateJob(jobRequest, job.getRelationships().getOrganization().getData().getId(), job.getId());
}

private boolean sendToExecutor(Job job, ExecutorJob executorJob) {
log.info("Sending Job: /n {}", executorJob);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<TerraformJob> response = restTemplate.postForEntity(this.executorUrl, terraformJob, TerraformJob.class);
ResponseEntity<ExecutorJob> response = restTemplate.postForEntity(this.executorUrl, executorJob, ExecutorJob.class);

log.info("Response Status: {}", response.getStatusCode().value());

if (response.getStatusCode().equals(HttpStatus.ACCEPTED)) {
JobRequest jobRequest = new JobRequest();
job.getAttributes().setStatus("queue");
jobRequest.setData(job);

restClient.updateJob(jobRequest, job.getRelationships().getOrganization().getData().getId(), job.getId());
}
terrakubeClient.updateJob(jobRequest, job.getRelationships().getOrganization().getData().getId(), job.getId());
return true;
} else
return false;
}
}

@Getter
@Setter
class TerraformJob {

private TerraformCommand terraformCommand;
private String organizationId;
private String workspaceId;
private String jobId;
private String terraformVersion;
private String source;
private String branch;
private String vcsType;
private String accessToken;
private HashMap<String, String> environmentVariables;
private HashMap<String, String> variables;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.azbuilder.api.schedule;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.azbuilder.api.client.TerrakubeClient;
import org.azbuilder.api.client.model.organization.job.Job;
import org.azbuilder.api.client.model.organization.job.step.Step;
import org.azbuilder.api.client.model.organization.job.step.StepAttributes;
import org.azbuilder.api.client.model.organization.job.step.StepRequest;
import org.azbuilder.api.schedule.yaml.Flow;
import org.azbuilder.api.schedule.yaml.FlowConfig;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

import java.util.*;

@AllArgsConstructor
@Service
@Slf4j
public class ScheduleJobService {

TerrakubeClient terrakubeClient;
JobService jobService;

@Scheduled(fixedRate = 60000)
public void searchPendingJobs() {
jobService.searchPendingJobs().parallelStream().forEach(job -> {

job = initialJobSetup(job);

Optional<Flow> flow = Optional.ofNullable(getNextFlow(job));
if (flow.isPresent()) {
log.info("Execute command: {} \n {}", flow.get().getType(), flow.get().getCommands());
String stepId = getCurrentStepId(job);
if(jobService.execute(job, stepId, flow.get()))
log.info("Executing Job {} Step Id {}", job.getId(), stepId);
} else {
jobService.completeJob(job);
}
});
}

private Job initialJobSetup(Job job) {
if (job.getRelationships().getStep().getData().isEmpty()) {

FlowConfig flowConfig = getFlowConfig(job.getAttributes().getTcl());
log.info("Custom Job Setup: \n {}", flowConfig.toString());

flowConfig.getFlow().parallelStream().forEach(flow -> {
log.info("Creating step: {}", flow.toString());
StepRequest stepRequest = new StepRequest();
Step newStep = new Step();
newStep.setType("step");
StepAttributes stepAttributes = new StepAttributes();
stepAttributes.setStatus("pending");
stepAttributes.setStepNumber(String.valueOf(flow.getStep()));
newStep.setAttributes(stepAttributes);
stepRequest.setData(newStep);
terrakubeClient.createStep(stepRequest, job.getRelationships().getOrganization().getData().getId(), job.getId());
});
return terrakubeClient.getJobById(job.getRelationships().getOrganization().getData().getId(), job.getId()).getData();
} else
return job;
}


private FlowConfig getFlowConfig(String tcl) {
Yaml yaml = new Yaml(new Constructor(FlowConfig.class));
FlowConfig temp = yaml.load(new String(Base64.getDecoder().decode(tcl)));
log.info("FlowConfig: \n {}", temp);
return yaml.load(new String(Base64.getDecoder().decode(tcl)));
}

private Flow getNextFlow(Job job) {
TreeMap<Integer, Step> map = getPendingSteps(job);

if (!map.isEmpty()) {
log.info("Next Command: {}", map.firstKey());
Optional<Flow> nextFlow = getFlowConfig(job.getAttributes().getTcl())
.getFlow()
.stream()
.filter(flow -> flow.getStep() == map.firstKey())
.findFirst();
return nextFlow.isPresent()? nextFlow.get(): null;
}
else
return null;
}

private TreeMap<Integer, Step> getPendingSteps(Job job) {
final TreeMap<Integer, Step> map = new TreeMap<>();
terrakubeClient.getJobById(job.getRelationships().getOrganization().getData().getId(), job.getId())
.getIncluded()
.stream()
.filter(step -> step.getAttributes().getStatus().equals("pending"))
.forEach(step -> map.put(Integer.valueOf(step.getAttributes().getStepNumber()), step));
return map;
}

public String getCurrentStepId(Job job) {
return getPendingSteps(job).firstEntry().getValue().getId();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.azbuilder.api.schedule.executor;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.azbuilder.api.schedule.yaml.Command;

import java.util.HashMap;
import java.util.List;

@ToString
@Getter
@Setter
public class ExecutorJob {
private List<Command> commandList;
private String type;
private String organizationId;
private String workspaceId;
private String jobId;
private String stepId;
private String terraformVersion;
private String source;
private String branch;
private String vcsType;
private String accessToken;
private HashMap<String, String> environmentVariables;
private HashMap<String, String> variables;
}
16 changes: 16 additions & 0 deletions api-job/src/main/java/org/azbuilder/api/schedule/yaml/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.azbuilder.api.schedule.yaml;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@ToString
@Getter
@Setter
public class Command {
private String runtime;
private String script;
private int priority;
private boolean before;
private boolean after;
}
Loading

0 comments on commit b903cbd

Please sign in to comment.