diff --git a/src/main/client/app/pages/stacks/job/job-step.vue b/src/main/client/app/pages/stacks/job/job-step.vue
index d74ae3167..722e47afc 100644
--- a/src/main/client/app/pages/stacks/job/job-step.vue
+++ b/src/main/client/app/pages/stacks/job/job-step.vue
@@ -78,7 +78,7 @@
diff --git a/src/main/client/app/pages/stacks/job/job.vue b/src/main/client/app/pages/stacks/job/job.vue
index 67fc79419..fffba6a69 100644
--- a/src/main/client/app/pages/stacks/job/job.vue
+++ b/src/main/client/app/pages/stacks/job/job.vue
@@ -41,7 +41,6 @@
applyJob,
deleteJob,
getJob,
- planJob,
retryJob,
} from '@/shared/api/jobs-api';
import { getStack } from '@/shared/api/stacks-api';
@@ -83,6 +82,7 @@
},
isSecondStepDoable() {
return this.job.status
+ && !this.job.status.includes('PENDING')
&& !this.job.status.includes('STARTED')
&& !this.job.status.includes('FAILED')
&& !this.job.status.includes('APPLY');
@@ -91,29 +91,20 @@
async created() {
[this.stack, this.job] = await Promise.all([getStack(this.stackId), this.refreshJobUntilCompletion()]);
this.loaded = true;
- if (!this.job.status) {
- await this.planJob();
- }
+
+ this.refreshIntervalId = setInterval(this.refreshJobUntilCompletion, INTERVAL_TIMEOUT);
},
destroyed() {
clearInterval(this.refreshIntervalId);
},
methods: {
- async planJob() {
- await planJob(this.jobId);
- await this.waitUntilJobStarted();
-
- this.refreshIntervalId = setInterval(this.refreshJobUntilCompletion, INTERVAL_TIMEOUT);
- },
async applyJob() {
await applyJob(this.jobId);
- await this.waitUntilJobStarted();
this.refreshIntervalId = setInterval(this.refreshJobUntilCompletion, INTERVAL_TIMEOUT);
},
async retryJob() {
await retryJob(this.jobId);
- await this.waitUntilJobStarted();
this.refreshIntervalId = setInterval(this.refreshJobUntilCompletion, INTERVAL_TIMEOUT);
},
@@ -132,24 +123,11 @@
},
async refreshJobUntilCompletion() {
this.job = await getJob(this.jobId);
- if (this.job.status && !this.job.status.includes('STARTED')) {
+ if (this.job.status.includes('FINISHED') || this.job.status.includes('FAILED')) {
clearInterval(this.refreshIntervalId);
}
return this.job;
},
- // wait until the job is in "started" state
- async waitUntilJobStarted() {
- const poll = async (resolve) => {
- this.job = await getJob(this.jobId);
- if (this.job.status && this.job.status.includes('STARTED')) {
- resolve();
- } else {
- setTimeout(() => poll(resolve), 500);
- }
- };
- return new Promise(poll);
- },
-
},
};
diff --git a/src/main/java/io/gaia_app/runner/DockerRunner.java b/src/main/java/io/gaia_app/runner/DockerRunner.java
deleted file mode 100644
index 46d6edebb..000000000
--- a/src/main/java/io/gaia_app/runner/DockerRunner.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package io.gaia_app.runner;
-
-import com.github.dockerjava.api.DockerClient;
-import com.github.dockerjava.api.async.ResultCallback;
-import com.github.dockerjava.api.command.CreateContainerCmd;
-import com.github.dockerjava.api.command.CreateContainerResponse;
-import com.github.dockerjava.api.exception.DockerException;
-import com.github.dockerjava.api.model.*;
-import io.gaia_app.settings.bo.Settings;
-import io.gaia_app.stacks.workflow.JobWorkflow;
-import org.apache.commons.io.output.WriterOutputStream;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.WritableByteChannel;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Service to run docker container
- */
-@Service
-public class DockerRunner {
-
- private static final Logger LOG = LoggerFactory.getLogger(DockerRunner.class);
-
- private DockerClient dockerClient;
-
- private Settings settings;
-
- @Autowired
- public DockerRunner(DockerClient dockerClient, Settings settings) {
- this.dockerClient = dockerClient;
- this.settings = settings;
- }
-
- int runContainerForJob(JobWorkflow jobWorkflow, String script) {
- try {
- var env = new ArrayList();
- env.add("TF_IN_AUTOMATION=true");
- env.addAll(settings.env());
-
- var job = jobWorkflow.getJob();
-
- // add credentials of the job, if any
- if (job.getCredentials() != null){
- env.addAll(job.getCredentials().toEnv());
- }
-
- // pulling the image
- dockerClient.pullImageCmd(job.getTerraformImage().image())
- .start()
- .awaitCompletion();
-
- // create a new container
- var createContainerCmd = dockerClient.createContainerCmd(job.getTerraformImage().image())
- // resetting entrypoint to empty
- .withEntrypoint(new String[]{})
- // using /bin/sh as command
- .withCmd("/bin/sh")
- // using env vars
- .withEnv(env)
- // open stdin
- .withTty(false)
- .withStdinOpen(true)
- // bind mount docker sock (to be able to use docker provider in terraform)
- .withHostConfig(HostConfig.newHostConfig().withBinds(new Bind("/var/run/docker.sock", new Volume("/var/run/docker.sock"))));
-
- var container = createContainerCmd.exec();
- var containerId = container.getId();
-
- // start the container
- dockerClient.startContainerCmd(containerId).exec();
-
- // attaching the outputs in a background thread
- var step = jobWorkflow.getCurrentStep();
-
- // write the content of the script to the container's std in
- try (
- var out = new PipedOutputStream();
- var in = new PipedInputStream(out);
- var containerLogsCallback = new ResultCallback.Adapter(){
- @Override
- public void onNext(Frame frame) {
- try {
- step.getLogsWriter().write(new String(frame.getPayload()));
- } catch (IOException e) {
- e.printStackTrace();
- }
- super.onNext(frame);
- }
- };
- ){
- dockerClient.attachContainerCmd(container.getId())
- .withFollowStream(true)
- .withStdOut(true)
- .withStdErr(true)
- .withStdIn(in)
- .exec(containerLogsCallback);
-
- out.write((script + "\n").getBytes());
- out.flush();
-
- containerLogsCallback.awaitCompletion(1800, TimeUnit.SECONDS);
- }
-
- // wait for the container to exit
- var containerExit = dockerClient.waitContainerCmd(containerId)
- .start()
- // with a timeout of 30 minutes
- .awaitStatusCode(1800, TimeUnit.SECONDS);
-
- dockerClient.removeContainerCmd(containerId).exec();
-
- return containerExit;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- LOG.error("Interrupted Exception", e);
- return 99;
- } catch (DockerException | IOException e) {
- LOG.error("Exception when running job", e);
- return 99;
- }
- }
-
-}
diff --git a/src/main/java/io/gaia_app/runner/StackCommandBuilder.java b/src/main/java/io/gaia_app/runner/RunnerCommandBuilder.java
similarity index 93%
rename from src/main/java/io/gaia_app/runner/StackCommandBuilder.java
rename to src/main/java/io/gaia_app/runner/RunnerCommandBuilder.java
index ae3f7adb0..3602f00bd 100644
--- a/src/main/java/io/gaia_app/runner/StackCommandBuilder.java
+++ b/src/main/java/io/gaia_app/runner/RunnerCommandBuilder.java
@@ -20,7 +20,7 @@
* A builder class to create stack commands
*/
@Component
-public class StackCommandBuilder {
+public class RunnerCommandBuilder {
private Settings settings;
private RunnerApiSecurityConfig.RunnerApiSecurityProperties runnerApiSecurityProperties;
@@ -28,7 +28,7 @@ public class StackCommandBuilder {
private List registryOAuth2Providers;
@Autowired
- StackCommandBuilder(Settings settings, Mustache terraformMustache, List registryOAuth2Providers, RunnerApiSecurityConfig.RunnerApiSecurityProperties runnerApiSecurityProperties) {
+ RunnerCommandBuilder(Settings settings, Mustache terraformMustache, List registryOAuth2Providers, RunnerApiSecurityConfig.RunnerApiSecurityProperties runnerApiSecurityProperties) {
this.settings = settings;
this.terraformMustache = terraformMustache;
this.registryOAuth2Providers = registryOAuth2Providers;
diff --git a/src/main/java/io/gaia_app/runner/RunnerController.java b/src/main/java/io/gaia_app/runner/RunnerController.java
index 3bd7a6288..162f7d217 100644
--- a/src/main/java/io/gaia_app/runner/RunnerController.java
+++ b/src/main/java/io/gaia_app/runner/RunnerController.java
@@ -1,11 +1,20 @@
package io.gaia_app.runner;
+import io.gaia_app.credentials.CredentialsService;
+import io.gaia_app.stacks.bo.*;
+import io.gaia_app.stacks.repository.JobRepository;
import io.gaia_app.stacks.repository.StackRepository;
+import io.gaia_app.stacks.repository.StepRepository;
+import io.gaia_app.stacks.workflow.JobWorkflow;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
/**
* Controller for the operations that are called by the runner only
@@ -17,9 +26,123 @@ public class RunnerController {
@Autowired
private StackRepository stackRepository;
+ @Autowired
+ private JobRepository jobRepository;
+
+ @Autowired
+ private StepRepository stepRepository;
+
+ @Autowired
+ private CredentialsService credentialsService;
+
+ @Autowired
+ private RunnerCommandBuilder runnerCommandBuilder;
+
@GetMapping(value = "/stacks/{id}.tfvars", produces = "text/plain")
public String tfvars(@PathVariable String id){
var stack = stackRepository.findById(id).orElseThrow();
return stack.tfvars();
}
+
+ /**
+ * Gets the first job step that can be run by the runner (one in "pending" state)
+ */
+ @GetMapping("/steps/request")
+ public Map findFirstRunnableJob() {
+ var job = this.jobRepository.findFirstByStatusEqualsOrStatusEquals(JobStatus.PLAN_PENDING, JobStatus.APPLY_PENDING)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ var stack = this.stackRepository.findById(job.getStackId()).orElseThrow();
+
+ // get the workflow
+ var workflow = new JobWorkflow(job);
+ // start the workflow
+ var step = workflow.startWorkflow();
+
+ // saving the job
+ this.jobRepository.save(job);
+ this.stepRepository.save(step);
+
+ var script = "";
+ // generate the script
+ if(job.getType() == JobType.RUN){
+ if(step.getType() == StepType.PLAN) {
+ script = runnerCommandBuilder.buildPlanScript(job, stack, stack.getModule());
+ }
+ else {
+ script = runnerCommandBuilder.buildApplyScript(job, stack, stack.getModule());
+ }
+ }
+ else { // destroy
+ if(step.getType() == StepType.PLAN) {
+ script = runnerCommandBuilder.buildPlanDestroyScript(job, stack, stack.getModule());
+ }
+ else {
+ script = runnerCommandBuilder.buildDestroyScript(job, stack, stack.getModule());
+ }
+ }
+
+ List env = Collections.emptyList();
+ if(stack.getCredentialsId() != null){
+ var credentials = this.credentialsService.load(stack.getCredentialsId()).orElse(null);
+ if(credentials != null){
+ env = credentials.toEnv();
+ }
+ }
+
+ return Map.of(
+ "step", step,
+ "script", script,
+ "env", env,
+ "image", job.getTerraformImage().image());
+ }
+
+ /**
+ * Updates the step logs
+ */
+ @PutMapping("/steps/{stepId}/logs")
+ public void updateLogs(@PathVariable String stepId, @RequestBody String logs) {
+ var step = this.stepRepository.findById(stepId).orElseThrow();
+ step.getLogs().add(logs);
+ this.stepRepository.save(step);
+ }
+
+ /**
+ * Updates the step status
+ */
+ @PutMapping("/steps/{stepId}/status")
+ public void updateStepStatus(@PathVariable String stepId, @RequestBody int status) {
+ var step = this.stepRepository.findById(stepId).orElseThrow();
+
+ // reload the job to check workflow status
+ var job = this.jobRepository.findById(step.getJobId()).orElseThrow();
+
+ // rebuild the workflow
+ var workflow = new JobWorkflow(job);
+ workflow.setCurrentStep(step);
+ workflow.next(status);
+
+ // save the job & step to update their status
+ this.stepRepository.save(step);
+ this.jobRepository.save(job);
+ }
+
+ /**
+ * Updates the job state
+ */
+ @PutMapping("/steps/{stepId}/start")
+ public void startStep(@PathVariable String stepId) {
+ // getting step
+ var step = this.stepRepository.findById(stepId).orElseThrow();
+
+ // reload the job to check workflow status
+ var job = this.jobRepository.findById(step.getJobId()).orElseThrow();
+
+ // rebuild the workflow and start it
+ var workflow = new JobWorkflow(job);
+ workflow.startWorkflow();
+
+ // save the job & step to update their status
+ this.stepRepository.save(step);
+ this.jobRepository.save(job);
+ }
}
diff --git a/src/main/java/io/gaia_app/runner/StackRunner.java b/src/main/java/io/gaia_app/runner/StackRunner.java
deleted file mode 100644
index 97e72c812..000000000
--- a/src/main/java/io/gaia_app/runner/StackRunner.java
+++ /dev/null
@@ -1,167 +0,0 @@
-package io.gaia_app.runner;
-
-import io.gaia_app.credentials.CredentialsService;
-import io.gaia_app.modules.bo.TerraformModule;
-import io.gaia_app.stacks.bo.Job;
-import io.gaia_app.stacks.bo.JobType;
-import io.gaia_app.stacks.bo.Stack;
-import io.gaia_app.stacks.bo.StackState;
-import io.gaia_app.stacks.repository.JobRepository;
-import io.gaia_app.stacks.repository.StackRepository;
-import io.gaia_app.stacks.repository.StepRepository;
-import io.gaia_app.stacks.workflow.JobWorkflow;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Async;
-import org.springframework.stereotype.Service;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.function.IntConsumer;
-import java.util.function.Supplier;
-
-/**
- * Runs a module instance
- */
-@Service
-public class StackRunner {
-
- private DockerRunner dockerRunner;
- private StackCommandBuilder stackCommandBuilder;
- private StackRepository stackRepository;
- private JobRepository jobRepository;
- private StepRepository stepRepository;
- private CredentialsService credentialsService;
-
- private Map jobs = new HashMap<>();
-
- @Autowired
- public StackRunner(DockerRunner dockerRunner, StackCommandBuilder stackCommandBuilder,
- StackRepository stackRepository, JobRepository jobRepository, StepRepository stepRepository, CredentialsService credentialsService) {
- this.dockerRunner = dockerRunner;
- this.stackCommandBuilder = stackCommandBuilder;
- this.stackRepository = stackRepository;
- this.jobRepository = jobRepository;
- this.stepRepository = stepRepository;
- this.credentialsService = credentialsService;
- }
-
- private String managePlanScript(Job job, Stack stack, TerraformModule module) {
- if (JobType.RUN == job.getType()) {
- return stackCommandBuilder.buildPlanScript(job, stack, module);
- }
- return stackCommandBuilder.buildPlanDestroyScript(job, stack, module);
- }
-
- private void managePlanResult(Integer result, JobWorkflow jobWorkflow, Stack stack) {
- if (result == 0) {
- // diff is empty
- jobWorkflow.end();
- } else if (result == 2) {
- // there is a diff, set the status of the stack to : "TO_UPDATE"
- if (StackState.NEW != stack.getState() && jobWorkflow.getJob().getType() != JobType.DESTROY) {
- stack.setState(StackState.TO_UPDATE);
- stackRepository.save(stack);
- }
- jobWorkflow.end();
- } else {
- // error
- jobWorkflow.fail();
- }
- }
-
- private String manageApplyScript(Job job, Stack stack, TerraformModule module) {
- if (JobType.RUN == job.getType()) {
- return stackCommandBuilder.buildApplyScript(job, stack, module);
- }
- return stackCommandBuilder.buildDestroyScript(job, stack, module);
- }
-
- private void manageApplyResult(Integer result, JobWorkflow jobWorkflow, Stack stack) {
- if (result == 0) {
- jobWorkflow.end();
-
- // update stack information
- stack.setState(jobWorkflow.getJob().getType() == JobType.RUN ? StackState.RUNNING : StackState.STOPPED);
- stackRepository.save(stack);
- } else {
- jobWorkflow.fail();
- }
- }
-
- /**
- * @param jobWorkflow
- * @param jobActionFn function applying o the job
- * @param scriptFn function allowing to get the right script to execute
- * @param resultFn function treating the result ot the executed script
- */
- private void treatJob(JobWorkflow jobWorkflow, Consumer jobActionFn,
- Supplier scriptFn, IntConsumer resultFn) {
- // execute the workflow of the job
- jobActionFn.accept(jobWorkflow);
-
- var job = jobWorkflow.getJob();
- this.jobs.put(job.getId(), job);
- stepRepository.saveAll(job.getSteps());
- jobRepository.save(job);
-
- // get the wanted script
- var script = scriptFn.get();
-
- var result = this.dockerRunner.runContainerForJob(jobWorkflow, script);
-
- // manage the result of the execution of the script
- resultFn.accept(result);
-
- // save job to database
- stepRepository.saveAll(job.getSteps());
- jobRepository.save(job);
- this.jobs.remove(job.getId());
- }
-
- @Async
- public void plan(JobWorkflow jobWorkflow, TerraformModule module, Stack stack) {
- if(stack.getCredentialsId() != null){
- jobWorkflow.getJob().setCredentials(this.credentialsService.load(stack.getCredentialsId()).orElseThrow());
- }
- treatJob(
- jobWorkflow,
- JobWorkflow::plan,
- () -> managePlanScript(jobWorkflow.getJob(), stack, module),
- result -> managePlanResult(result, jobWorkflow, stack)
- );
- }
-
- @Async
- public void apply(JobWorkflow jobWorkflow, TerraformModule module, Stack stack) {
- if(stack.getCredentialsId() != null){
- jobWorkflow.getJob().setCredentials(this.credentialsService.load(stack.getCredentialsId()).orElseThrow());
- }
- treatJob(
- jobWorkflow,
- JobWorkflow::apply,
- () -> manageApplyScript(jobWorkflow.getJob(), stack, module),
- result -> manageApplyResult(result, jobWorkflow, stack)
- );
- }
-
- @Async
- public void retry(JobWorkflow jobWorkflow, TerraformModule module, Stack stack) {
- if(stack.getCredentialsId() != null){
- jobWorkflow.getJob().setCredentials(this.credentialsService.load(stack.getCredentialsId()).orElseThrow());
- }
- stepRepository.deleteByJobId(jobWorkflow.getJob().getId());
- treatJob(
- jobWorkflow,
- JobWorkflow::retry,
- () -> managePlanScript(jobWorkflow.getJob(), stack, module),
- result -> managePlanResult(result, jobWorkflow, stack)
- );
- }
-
- public Optional getJob(String jobId) {
- return Optional.ofNullable(this.jobs.get(jobId));
- }
-
-}
diff --git a/src/main/java/io/gaia_app/runner/StackRunnerException.java b/src/main/java/io/gaia_app/runner/StackRunnerException.java
deleted file mode 100644
index 1d3234146..000000000
--- a/src/main/java/io/gaia_app/runner/StackRunnerException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.gaia_app.runner;
-
-class StackRunnerException extends Exception {
-
- StackRunnerException(String message, Throwable cause) {
- super(message, cause);
- }
-
-}
diff --git a/src/main/java/io/gaia_app/runner/config/DockerConfig.java b/src/main/java/io/gaia_app/runner/config/DockerConfig.java
deleted file mode 100644
index 4a602b3f3..000000000
--- a/src/main/java/io/gaia_app/runner/config/DockerConfig.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package io.gaia_app.runner.config;
-
-import com.github.dockerjava.api.DockerClient;
-import com.github.dockerjava.core.DefaultDockerClientConfig;
-import com.github.dockerjava.core.DockerClientConfig;
-import com.github.dockerjava.core.DockerClientImpl;
-import com.github.dockerjava.okhttp.OkHttpDockerCmdExecFactory;
-import io.gaia_app.settings.bo.Settings;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * Configuration of the docker transport
- */
-@Configuration
-public class DockerConfig {
-
- /**
- * builds the DockerClientConfig based on Gaia's settings
- * @param settings Gaia's settings
- * @return a docker client configuration
- */
- @Bean
- DockerClientConfig dockerClientConfig(Settings settings) {
- return DefaultDockerClientConfig.createDefaultConfigBuilder()
- .withDockerHost(settings.getDockerDaemonUrl())
- .build();
- }
-
- /**
- * builds the docker client
- * @param config the configuration (host/registries...)
- * @return
- */
- @Bean
- DockerClient client(DockerClientConfig config){
- return DockerClientImpl.getInstance(config)
- .withDockerCmdExecFactory(new OkHttpDockerCmdExecFactory());
- }
-
-}
diff --git a/src/main/java/io/gaia_app/stacks/bo/Job.java b/src/main/java/io/gaia_app/stacks/bo/Job.java
index 9d8a8e30a..3c5cfeba0 100644
--- a/src/main/java/io/gaia_app/stacks/bo/Job.java
+++ b/src/main/java/io/gaia_app/stacks/bo/Job.java
@@ -61,7 +61,7 @@ public void proceed(JobStatus jobStatus) {
}
public void reset() {
- this.status = null;
+ this.status = JobStatus.PLAN_PENDING;
this.startDateTime = null;
this.endDateTime = null;
this.steps.clear();
diff --git a/src/main/java/io/gaia_app/stacks/bo/JobStatus.java b/src/main/java/io/gaia_app/stacks/bo/JobStatus.java
index 8d963c2ff..a23e26c63 100644
--- a/src/main/java/io/gaia_app/stacks/bo/JobStatus.java
+++ b/src/main/java/io/gaia_app/stacks/bo/JobStatus.java
@@ -1,6 +1,6 @@
package io.gaia_app.stacks.bo;
public enum JobStatus {
- PLAN_STARTED, PLAN_FINISHED, PLAN_FAILED,
- APPLY_STARTED, APPLY_FINISHED, APPLY_FAILED,
+ PLAN_PENDING, PLAN_STARTED, PLAN_FINISHED, PLAN_FAILED,
+ APPLY_PENDING, APPLY_STARTED, APPLY_FINISHED, APPLY_FAILED,
}
diff --git a/src/main/java/io/gaia_app/stacks/bo/Step.java b/src/main/java/io/gaia_app/stacks/bo/Step.java
index d7ba7da6f..0fc6fc211 100644
--- a/src/main/java/io/gaia_app/stacks/bo/Step.java
+++ b/src/main/java/io/gaia_app/stacks/bo/Step.java
@@ -1,12 +1,9 @@
package io.gaia_app.stacks.bo;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import org.springframework.data.annotation.Transient;
-
-import java.io.StringWriter;
-import java.io.Writer;
import java.time.Duration;
import java.time.LocalDateTime;
+import java.util.LinkedList;
+import java.util.List;
import java.util.UUID;
/**
@@ -21,9 +18,7 @@ public class Step {
private Long executionTime;
private StepType type;
private StepStatus status;
- @Transient
- private StringWriter logsWriter = new StringWriter();
- private String logs;
+ private List logs = new LinkedList<>();
public Step() {
}
@@ -42,14 +37,12 @@ public void start() {
public void end() {
this.endDateTime = LocalDateTime.now();
this.executionTime = Duration.between(this.startDateTime, this.endDateTime).toMillis();
- this.logs = this.logsWriter.toString();
this.status = StepStatus.FINISHED;
}
public void fail() {
this.endDateTime = LocalDateTime.now();
this.executionTime = Duration.between(this.startDateTime, this.endDateTime).toMillis();
- this.logs = this.logsWriter.toString();
this.status = StepStatus.FAILED;
}
@@ -109,20 +102,12 @@ public void setStatus(StepStatus status) {
this.status = status;
}
- public String getLogs() {
- if (status == StepStatus.FINISHED || status == StepStatus.FAILED) {
- return logs;
- }
- return logsWriter.toString();
+ public List getLogs() {
+ return logs;
}
- public void setLogs(String logs) {
+ public void setLogs(List logs) {
this.logs = logs;
}
- @JsonIgnore
- public Writer getLogsWriter() {
- return logsWriter;
- }
-
}
diff --git a/src/main/java/io/gaia_app/stacks/controller/JobRestController.kt b/src/main/java/io/gaia_app/stacks/controller/JobRestController.kt
index bc0e4dab3..d6e55cb01 100644
--- a/src/main/java/io/gaia_app/stacks/controller/JobRestController.kt
+++ b/src/main/java/io/gaia_app/stacks/controller/JobRestController.kt
@@ -1,8 +1,7 @@
package io.gaia_app.stacks.controller
-import io.gaia_app.modules.repository.TerraformModuleRepository
-import io.gaia_app.runner.StackRunner
import io.gaia_app.stacks.bo.Job
+import io.gaia_app.stacks.bo.JobStatus
import io.gaia_app.stacks.repository.JobRepository
import io.gaia_app.stacks.repository.StackRepository
import io.gaia_app.stacks.repository.StepRepository
@@ -14,8 +13,6 @@ import org.springframework.web.bind.annotation.*
@RequestMapping("/api/jobs")
class JobRestController(
private val jobRepository: JobRepository,
- private val stackRepository: StackRepository,
- private val stackRunner: StackRunner,
private val stepRepository: StepRepository) {
@GetMapping(params = ["stackId"])
@@ -23,32 +20,30 @@ class JobRestController(
@GetMapping("/{id}")
fun job(@PathVariable id: String): Job {
- val runningJob = stackRunner.getJob(id);
- if (runningJob.isPresent) {
- return runningJob.get();
- }
return jobRepository.findById(id).orElseThrow { JobNotFoundException() }
}
@PostMapping("/{id}/plan")
fun plan(@PathVariable id: String) {
val job = jobRepository.findById(id).orElseThrow { JobNotFoundException() }
- val stack = stackRepository.findById(job.stackId).orElseThrow()
- stackRunner.plan(JobWorkflow(job), stack.module, stack)
+ val workflow = JobWorkflow(job)
+ workflow.plan()
+ jobRepository.save(job)
}
@PostMapping("/{id}/apply")
fun apply(@PathVariable id: String) {
val job = jobRepository.findById(id).orElseThrow { JobNotFoundException() }
- val stack = stackRepository.findById(job.stackId).orElseThrow()
- stackRunner.apply(JobWorkflow(job), stack.module, stack)
+ job.status = JobStatus.APPLY_PENDING
+ jobRepository.save(job)
}
@PostMapping("/{id}/retry")
fun retry(@PathVariable id: String) {
val job = jobRepository.findById(id).orElseThrow { JobNotFoundException() }
- val stack = stackRepository.findById(job.stackId).orElseThrow()
- stackRunner.retry(JobWorkflow(job), stack.module, stack)
+ val workflow = JobWorkflow(job)
+ workflow.retry()
+ jobRepository.save(job)
}
@DeleteMapping("/{id}")
diff --git a/src/main/java/io/gaia_app/stacks/controller/StackRestController.java b/src/main/java/io/gaia_app/stacks/controller/StackRestController.java
index 1b85424ea..cbdb7b88d 100644
--- a/src/main/java/io/gaia_app/stacks/controller/StackRestController.java
+++ b/src/main/java/io/gaia_app/stacks/controller/StackRestController.java
@@ -2,6 +2,7 @@
import io.gaia_app.credentials.CredentialsRepository;
import io.gaia_app.stacks.bo.Job;
+import io.gaia_app.stacks.bo.JobStatus;
import io.gaia_app.stacks.bo.JobType;
import io.gaia_app.stacks.bo.Stack;
import io.gaia_app.stacks.repository.JobRepository;
@@ -90,6 +91,9 @@ public Map launchJob(@PathVariable String id, @PathVariable JobT
// create a new job
var job = new Job(jobType, id, user);
+ // setting the status to pending
+ job.setStatus(JobStatus.PLAN_PENDING);
+
job.setTerraformImage(stack.getModule().getTerraformImage());
if(stack.getCredentialsId() != null){
this.credentialsRepository.findById(stack.getCredentialsId())
diff --git a/src/main/java/io/gaia_app/stacks/repository/JobRepository.kt b/src/main/java/io/gaia_app/stacks/repository/JobRepository.kt
index f8d4052ac..364ecb358 100644
--- a/src/main/java/io/gaia_app/stacks/repository/JobRepository.kt
+++ b/src/main/java/io/gaia_app/stacks/repository/JobRepository.kt
@@ -1,8 +1,11 @@
package io.gaia_app.stacks.repository
import io.gaia_app.stacks.bo.Job
+import io.gaia_app.stacks.bo.JobStatus
+import org.jetbrains.annotations.NotNull
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Repository
+import java.util.*
/**
* Repository for jobs
@@ -12,4 +15,6 @@ interface JobRepository : MongoRepository {
fun findAllByStackIdOrderByStartDateTimeDesc(stackId: String): List
+ fun findFirstByStatusEqualsOrStatusEquals(planPending: JobStatus, applyPending: JobStatus): @NotNull Optional
+
}
diff --git a/src/main/java/io/gaia_app/stacks/workflow/JobWorkflow.java b/src/main/java/io/gaia_app/stacks/workflow/JobWorkflow.java
index 9fafb3dee..2eb9392f5 100644
--- a/src/main/java/io/gaia_app/stacks/workflow/JobWorkflow.java
+++ b/src/main/java/io/gaia_app/stacks/workflow/JobWorkflow.java
@@ -1,8 +1,6 @@
package io.gaia_app.stacks.workflow;
-import io.gaia_app.stacks.bo.Job;
-import io.gaia_app.stacks.bo.JobStatus;
-import io.gaia_app.stacks.bo.Step;
+import io.gaia_app.stacks.bo.*;
import io.gaia_app.stacks.workflow.state.*;
import io.gaia_app.stacks.workflow.state.*;
@@ -83,6 +81,9 @@ JobState evalInitialState(JobStatus jobStatus) {
case PLAN_FAILED:
result = new PlanFailedState();
break;
+ case APPLY_PENDING:
+ result = new PlanFinishedState();
+ break;
case APPLY_STARTED:
result = new ApplyStartedState();
break;
@@ -95,4 +96,47 @@ JobState evalInitialState(JobStatus jobStatus) {
}
return result;
}
+
+ public Step startWorkflow() {
+ if(this.job.getStatus() == JobStatus.PLAN_PENDING){
+ this.state.plan(this);
+ }
+ else if(this.job.getStatus() == JobStatus.APPLY_PENDING) {
+ this.state.apply(this);
+ }
+ return this.currentStep;
+ }
+
+ /**
+ * Updates workflow to next status depending of the result code
+ * @param stepResultCode
+ */
+ public void next(int stepResultCode) {
+ if(this.job.getStatus() == JobStatus.PLAN_STARTED){
+ this.managePlanResult(stepResultCode);
+ }
+ else if(this.job.getStatus() == JobStatus.APPLY_STARTED) {
+ this.managerApplyResult(stepResultCode);
+ }
+ }
+
+ private void managePlanResult(int result) {
+ if (result == 0) {
+ // diff is empty
+ this.end();
+ } else if (result == 2) {
+ this.end();
+ } else {
+ // error
+ this.fail();
+ }
+ }
+
+ private void managerApplyResult(int result){
+ if (result == 0) {
+ this.end();
+ } else {
+ this.fail();
+ }
+ }
}
diff --git a/src/main/java/io/gaia_app/stacks/workflow/state/RetryableState.java b/src/main/java/io/gaia_app/stacks/workflow/state/RetryableState.java
index 28136b1a8..dcf12e0d8 100644
--- a/src/main/java/io/gaia_app/stacks/workflow/state/RetryableState.java
+++ b/src/main/java/io/gaia_app/stacks/workflow/state/RetryableState.java
@@ -8,6 +8,5 @@ interface RetryableState extends JobState {
default void retry(JobWorkflow jobWorkflow) {
jobWorkflow.getJob().reset();
jobWorkflow.setState(new NotStartedState());
- jobWorkflow.plan();
}
}
diff --git a/src/test/java/io/gaia_app/runner/DockerRunnerIT.kt b/src/test/java/io/gaia_app/runner/DockerRunnerIT.kt
deleted file mode 100644
index 34e1f5a79..000000000
--- a/src/test/java/io/gaia_app/runner/DockerRunnerIT.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-package io.gaia_app.runner
-
-import io.gaia_app.credentials.AWSCredentials
-import io.gaia_app.modules.bo.TerraformImage
-import io.gaia_app.runner.config.DockerConfig
-import io.gaia_app.settings.bo.Settings
-import io.gaia_app.stacks.bo.Job
-import io.gaia_app.stacks.bo.Step
-import io.gaia_app.stacks.workflow.JobWorkflow
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Assert
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Test
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.context.properties.EnableConfigurationProperties
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.test.context.TestPropertySource
-
-@SpringBootTest(classes = [DockerRunner::class, DockerConfig::class, Settings::class])
-@EnableConfigurationProperties
-@TestPropertySource(properties = ["gaia.dockerDaemonUrl=unix:///var/run/docker.sock"])
-class DockerRunnerIT {
-
- @Autowired
- private lateinit var dockerRunner: DockerRunner
-
- @Test
- fun `runContainerForJob() should work with a simple script`() {
- val script = "echo 'Hello World'; exit 0;"
-
- val job = Job()
- job.terraformImage = TerraformImage.defaultInstance()
- val jobWorkflow = JobWorkflow(job)
- jobWorkflow.currentStep = Step()
-
- assertEquals(0, dockerRunner.runContainerForJob(jobWorkflow, script).toLong())
- }
-
- @Test
- fun `runContainerForJob() should stop work with a simple script`() {
- val script = "set -e; echo 'Hello World'; false; exit 0;"
-
- val job = Job()
- job.terraformImage = TerraformImage.defaultInstance()
- val jobWorkflow = JobWorkflow(job)
- jobWorkflow.currentStep = Step()
-
- assertEquals(1, dockerRunner.runContainerForJob(jobWorkflow, script).toLong())
- }
-
- @Test
- fun `runContainerForJob() should return the script exit code`() {
- val script = "exit 5"
-
- val job = Job()
- job.terraformImage = TerraformImage.defaultInstance()
- val jobWorkflow = JobWorkflow(job)
- jobWorkflow.currentStep = Step()
-
- Assert.assertEquals(5, dockerRunner.runContainerForJob(jobWorkflow, script).toLong())
- }
-
- @Test
- fun `runContainerForJob() should feed step with container logs`() {
- val script = "echo 'hello world'; exit 0;"
-
- val job = Job()
- job.terraformImage = TerraformImage.defaultInstance()
- val jobWorkflow = JobWorkflow(job)
- jobWorkflow.currentStep = Step()
-
- dockerRunner.runContainerForJob(jobWorkflow, script)
-
- assertThat(jobWorkflow.currentStep.logs).isEqualTo("hello world\n");
- }
-
- @Test
- fun `runContainerForJob() use credentials of the job`() {
- val script = "echo \$AWS_ACCESS_KEY_ID; exit 0;"
-
- val job = Job()
- job.terraformImage = TerraformImage.defaultInstance()
- job.credentials = AWSCredentials("SOME_ACCESS_KEY", "SOME_SECRET_KEY")
- val jobWorkflow = JobWorkflow(job)
- jobWorkflow.currentStep = Step()
-
- dockerRunner.runContainerForJob(jobWorkflow, script)
-
- assertThat(jobWorkflow.currentStep.logs).isEqualTo("SOME_ACCESS_KEY\n");
- }
-
- @Test
- fun `runContainerForJob() use TF_IN_AUTOMATION env var`() {
- val script = "echo \$TF_IN_AUTOMATION; exit 0;"
-
- val job = Job()
- job.terraformImage = TerraformImage.defaultInstance()
- val jobWorkflow = JobWorkflow(job)
- jobWorkflow.currentStep = Step()
-
- dockerRunner.runContainerForJob(jobWorkflow, script)
-
- assertThat(jobWorkflow.currentStep.logs).isEqualTo("true\n");
- }
-}
diff --git a/src/test/java/io/gaia_app/runner/StackCommandBuilderTest.java b/src/test/java/io/gaia_app/runner/RunnerCommandBuilderTest.java
similarity index 92%
rename from src/test/java/io/gaia_app/runner/StackCommandBuilderTest.java
rename to src/test/java/io/gaia_app/runner/RunnerCommandBuilderTest.java
index c3b2a9fdc..8e09ea5e2 100644
--- a/src/test/java/io/gaia_app/runner/StackCommandBuilderTest.java
+++ b/src/test/java/io/gaia_app/runner/RunnerCommandBuilderTest.java
@@ -24,12 +24,12 @@
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
-class StackCommandBuilderTest {
+class RunnerCommandBuilderTest {
@Mock
RegistryOAuth2Provider registryOAuth2Provider;
- private StackCommandBuilder stackCommandBuilder;
+ private RunnerCommandBuilder runnerCommandBuilder;
@BeforeEach
void setup() {
@@ -37,7 +37,7 @@ void setup() {
var stateApiSecurityProperties = new RunnerApiSecurityConfig.RunnerApiSecurityProperties("gaia-backend", "password");
- stackCommandBuilder = new StackCommandBuilder(new Settings(), mustache, List.of(registryOAuth2Provider), stateApiSecurityProperties);
+ runnerCommandBuilder = new RunnerCommandBuilder(new Settings(), mustache, List.of(registryOAuth2Provider), stateApiSecurityProperties);
}
@Test
@@ -47,7 +47,7 @@ void buildPlanScript_shouldGenerateAFullScript() {
stack.setModule(module);
var job = new Job();
- var script = stackCommandBuilder.buildPlanScript(job, stack, module);
+ var script = runnerCommandBuilder.buildPlanScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning git://test' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -67,7 +67,7 @@ void buildPlanScript_shouldGenerateAFullScript_forAModuleWithoutDirectory() {
stack.setModule(module);
var job = new Job();
- var script = stackCommandBuilder.buildPlanScript(job, stack, module);
+ var script = runnerCommandBuilder.buildPlanScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning git://test' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -89,7 +89,7 @@ void buildPlanScript_shouldGenerateAFullScript_forAModuleWithAccessToken() {
when(registryOAuth2Provider.isAssignableFor(anyString())).thenReturn(true);
when(registryOAuth2Provider.getOAuth2Url(anyString(), anyString())).thenReturn("url_with_token");
- var script = stackCommandBuilder.buildPlanScript(job, stack, module);
+ var script = runnerCommandBuilder.buildPlanScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning url_with_token' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -110,7 +110,7 @@ void buildApplyScript_shouldGenerateAFullScript() {
stack.setModule(module);
var job = new Job();
- var script = stackCommandBuilder.buildApplyScript(job, stack, module);
+ var script = runnerCommandBuilder.buildApplyScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning git://test' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -130,7 +130,7 @@ void buildApplyScript_shouldGenerateAFullScript_forAModuleWithoutDirectory() {
stack.setModule(module);
var job = new Job();
- var script = stackCommandBuilder.buildApplyScript(job, stack, module);
+ var script = runnerCommandBuilder.buildApplyScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning git://test' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -152,7 +152,7 @@ void buildApplyScript_shouldGenerateAFullScript_forAModuleWithAccessToken() {
when(registryOAuth2Provider.isAssignableFor(anyString())).thenReturn(true);
when(registryOAuth2Provider.getOAuth2Url(anyString(), anyString())).thenReturn("url_with_token");
- var script = stackCommandBuilder.buildApplyScript(job, stack, module);
+ var script = runnerCommandBuilder.buildApplyScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning url_with_token' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -173,7 +173,7 @@ void buildPlanDestroyScript_shouldGenerateAFullScript() {
stack.setModule(module);
var job = new Job();
- var script = stackCommandBuilder.buildPlanDestroyScript(job, stack, module);
+ var script = runnerCommandBuilder.buildPlanDestroyScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning git://test' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -193,7 +193,7 @@ void buildPlanDestroyScript_shouldGenerateAFullScript_forAModuleWithoutDirectory
stack.setModule(module);
var job = new Job();
- var script = stackCommandBuilder.buildPlanDestroyScript(job, stack, module);
+ var script = runnerCommandBuilder.buildPlanDestroyScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning git://test' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -215,7 +215,7 @@ void buildPlanDestroyScript_shouldGenerateAFullScript_forAModuleWithAccessToken(
when(registryOAuth2Provider.isAssignableFor(anyString())).thenReturn(true);
when(registryOAuth2Provider.getOAuth2Url(anyString(), anyString())).thenReturn("url_with_token");
- var script = stackCommandBuilder.buildPlanDestroyScript(job, stack, module);
+ var script = runnerCommandBuilder.buildPlanDestroyScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning url_with_token' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -236,7 +236,7 @@ void buildDestroyScript_shouldGenerateAFullScript() {
stack.setModule(module);
var job = new Job();
- var script = stackCommandBuilder.buildDestroyScript(job, stack, module);
+ var script = runnerCommandBuilder.buildDestroyScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning git://test' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -256,7 +256,7 @@ void buildDestroyScript_shouldGenerateAFullScript_forAModuleWithoutDirectory() {
stack.setModule(module);
var job = new Job();
- var script = stackCommandBuilder.buildDestroyScript(job, stack, module);
+ var script = runnerCommandBuilder.buildDestroyScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning git://test' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
@@ -278,7 +278,7 @@ void buildDestroyScript_shouldGenerateAFullScript_forAModuleWithAccessToken() {
when(registryOAuth2Provider.isAssignableFor(anyString())).thenReturn(true);
when(registryOAuth2Provider.getOAuth2Url(anyString(), anyString())).thenReturn("url_with_token");
- var script = stackCommandBuilder.buildDestroyScript(job, stack, module);
+ var script = runnerCommandBuilder.buildDestroyScript(job, stack, module);
assertTrue(script.contains("echo '[gaia] using image hashicorp/terraform:latest'"));
assertTrue(script.contains("echo '[gaia] cloning url_with_token' | awk '{ sub(/oauth2:(.*)@/, \"oauth2:[MASKED]@\");}1'"));
diff --git a/src/test/java/io/gaia_app/runner/StackRunnerTest.java b/src/test/java/io/gaia_app/runner/StackRunnerTest.java
deleted file mode 100644
index 5fe7e03a6..000000000
--- a/src/test/java/io/gaia_app/runner/StackRunnerTest.java
+++ /dev/null
@@ -1,450 +0,0 @@
-package io.gaia_app.runner;
-
-import io.gaia_app.credentials.AWSCredentials;
-import io.gaia_app.credentials.CredentialsService;
-import io.gaia_app.modules.bo.TerraformModule;
-import io.gaia_app.stacks.bo.Job;
-import io.gaia_app.stacks.bo.JobType;
-import io.gaia_app.stacks.bo.Stack;
-import io.gaia_app.stacks.bo.StackState;
-import io.gaia_app.stacks.repository.JobRepository;
-import io.gaia_app.stacks.repository.StackRepository;
-import io.gaia_app.stacks.repository.StepRepository;
-import io.gaia_app.stacks.workflow.JobWorkflow;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-import java.util.Optional;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.*;
-
-@ExtendWith(MockitoExtension.class)
-class StackRunnerTest {
-
- @Mock
- private DockerRunner dockerRunner;
-
- @Mock
- private StackCommandBuilder stackCommandBuilder;
-
- @Mock
- private StackRepository stackRepository;
-
- @Mock
- private JobRepository jobRepository;
-
- @Mock
- private StepRepository stepRepository;
-
- @Mock
- private CredentialsService credentialsService;
-
- @Mock
- private JobWorkflow jobWorkflow;
-
- private Job job;
-
- private TerraformModule module;
-
- private Stack stack;
-
- @InjectMocks
- private StackRunner stackRunner;
-
- @BeforeEach
- void setUp() {
- stack = new Stack();
- job = new Job(JobType.RUN, null, null);
- lenient().when(jobWorkflow.getJob()).thenReturn(job); // use lenient to prevent mockito from throwing exception for tests not needing this mock (getLog_*)
- module = new TerraformModule();
- }
-
- @Test
- void plan_shouldExecutePlanWorkflow() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).plan();
- }
-
- @Test
- void plan_shouldUsePlanScript_WhenJobIsRun() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- verify(stackCommandBuilder).buildPlanScript(jobWorkflow.getJob(), stack, module);
- }
-
- @Test
- void plan_shouldUsePlanDestroyScript_WhenJobIsStop() {
- // given
- job.setType(JobType.DESTROY);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- verify(stackCommandBuilder).buildPlanDestroyScript(jobWorkflow.getJob(), stack, module);
- }
-
- @Test
- void plan_shouldEndJob_WhenSuccessful() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).end();
- }
-
- @Test
- void plan_shouldEndJob_WhenThereIsADiff() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).end();
- }
-
- @Test
- void plan_shouldFailJob_WhenThereIsAError() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(99);
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).fail();
- }
-
- @Test
- void plan_shouldUpdateStack_WhenThereIsADiff() {
- // given
- stack.setState(StackState.RUNNING);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- assertEquals(StackState.TO_UPDATE, stack.getState());
- verify(stackRepository).save(stack);
- }
-
- @Test
- void plan_shouldNotUpdateStack_WhenThereIsADiffForNewStacks() {
- // given
- stack.setState(StackState.NEW);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- assertEquals(StackState.NEW, stack.getState());
- verifyNoInteractions(stackRepository);
- }
-
- @Test
- void plan_shouldNotUpdateStack_WhenThereIsADiffAndJobIsStop() {
- // given
- stack.setState(StackState.RUNNING);
- job.setType(JobType.DESTROY);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- assertEquals(StackState.RUNNING, stack.getState());
- verifyNoInteractions(stackRepository);
- }
-
- @Test
- void plan_shouldSaveJobAndSteps() {
- // when
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- verify(jobRepository, times(2)).save(job);
- verify(stepRepository, times(2)).saveAll(job.getSteps());
- }
-
- @Test
- void apply_shouldExecuteApplyWorkflow() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.apply(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).apply();
- }
-
- @Test
- void apply_shouldUseApplyScript_WhenJobIsRun() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.apply(jobWorkflow, module, stack);
-
- // then
- verify(stackCommandBuilder).buildApplyScript(jobWorkflow.getJob(), stack, module);
- }
-
- @Test
- void apply_shouldUseDestroyScript_WhenJobIsStop() {
- // given
- job.setType(JobType.DESTROY);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.apply(jobWorkflow, module, stack);
-
- // then
- verify(stackCommandBuilder).buildDestroyScript(jobWorkflow.getJob(), stack, module);
- }
-
- @Test
- void apply_shouldEndJob_WhenSuccessful() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.apply(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).end();
- }
-
- @Test
- void apply_shouldFailJob_WhenThereIsAError() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(99);
- stackRunner.apply(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).fail();
- }
-
- @Test
- void apply_shouldUpdateStack_WhenSuccessfulAndJobIsRun() {
- // given
- stack.setState(StackState.NEW);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.apply(jobWorkflow, module, stack);
-
- // then
- assertEquals(StackState.RUNNING, stack.getState());
- verify(stackRepository).save(stack);
- }
-
- @Test
- void apply_shouldUpdateStack_WhenSuccessfulAndJobIsStop() {
- // given
- stack.setState(StackState.RUNNING);
- job.setType(JobType.DESTROY);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.apply(jobWorkflow, module, stack);
-
- // then
- assertEquals(StackState.STOPPED, stack.getState());
- verify(stackRepository).save(stack);
- }
-
- @Test
- void apply_shouldSaveJobAndSteps() {
- // when
- stackRunner.apply(jobWorkflow, module, stack);
-
- // then
- verify(jobRepository, times(2)).save(job);
- verify(stepRepository, times(2)).saveAll(job.getSteps());
- }
-
- @Test
- void retry_shouldDeletePreviousSteps() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- verify(stepRepository).deleteByJobId(jobWorkflow.getJob().getId());
- }
-
- @Test
- void retry_shouldExecuteRetryWorkflow() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).retry();
- }
-
- @Test
- void retry_shouldUsePlanScript_WhenJobIsRun() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- verify(stackCommandBuilder).buildPlanScript(jobWorkflow.getJob(), stack, module);
- }
-
- @Test
- void retry_shouldUsePlanDestroyScript_WhenJobIsStop() {
- // given
- job.setType(JobType.DESTROY);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- verify(stackCommandBuilder).buildPlanDestroyScript(jobWorkflow.getJob(), stack, module);
- }
-
- @Test
- void retry_shouldEndJob_WhenSuccessful() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).end();
- }
-
- @Test
- void retry_shouldEndJob_WhenThereIsADiff() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).end();
- }
-
- @Test
- void retry_shouldFailJob_WhenThereIsAError() {
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(99);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- verify(jobWorkflow).fail();
- }
-
- @Test
- void retry_shouldUpdateStack_WhenThereIsADiff() {
- // given
- stack.setState(StackState.RUNNING);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- assertEquals(StackState.TO_UPDATE, stack.getState());
- verify(stackRepository).save(stack);
- }
-
- @Test
- void retry_shouldNotUpdateStack_WhenThereIsADiffForNewStacks() {
- // given
- stack.setState(StackState.NEW);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- assertEquals(StackState.NEW, stack.getState());
- verifyNoInteractions(stackRepository);
- }
-
- @Test
- void retry_shouldNotUpdateStack_WhenThereIsADiffAndJobIsStop() {
- // given
- stack.setState(StackState.RUNNING);
- job.setType(JobType.DESTROY);
-
- // when
- when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- assertEquals(StackState.RUNNING, stack.getState());
- verifyNoInteractions(stackRepository);
- }
-
- @Test
- void retry_shouldSaveJobAndSteps() {
- // when
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- verify(jobRepository, times(2)).save(job);
- verify(stepRepository, times(2)).saveAll(job.getSteps());
- }
-
- @Test
- void credentialsShouldBeLoadedAndInjected_whenRunningAPlan(){
- // given
- stack.setCredentialsId("dummy");
- var credentials = new AWSCredentials("dummy","dummy");
-
- when(credentialsService.load("dummy")).thenReturn(Optional.of(credentials));
-
- // when
- stackRunner.plan(jobWorkflow, module, stack);
-
- // then
- verify(credentialsService).load("dummy");
- assertThat(job.getCredentials()).isEqualTo(credentials);
- }
-
- @Test
- void credentialsShouldBeLoadedAndInjected_whenRunningAnApply(){
- // given
- stack.setCredentialsId("dummy");
- var credentials = new AWSCredentials("dummy","dummy");
-
- when(credentialsService.load("dummy")).thenReturn(Optional.of(credentials));
-
- // when
- stackRunner.apply(jobWorkflow, module, stack);
-
- // then
- verify(credentialsService).load("dummy");
- assertThat(job.getCredentials()).isEqualTo(credentials);
- }
-
- @Test
- void credentialsShouldBeLoadedAndInjected_whenRunningARetry(){
- // given
- stack.setCredentialsId("dummy");
- var credentials = new AWSCredentials("dummy","dummy");
-
- when(credentialsService.load("dummy")).thenReturn(Optional.of(credentials));
-
- // when
- stackRunner.retry(jobWorkflow, module, stack);
-
- // then
- verify(credentialsService).load("dummy");
- assertThat(job.getCredentials()).isEqualTo(credentials);
- }
-
-}
diff --git a/src/test/java/io/gaia_app/runner/config/DockerConfigTest.java b/src/test/java/io/gaia_app/runner/config/DockerConfigTest.java
deleted file mode 100644
index a97c15f41..000000000
--- a/src/test/java/io/gaia_app/runner/config/DockerConfigTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package io.gaia_app.runner.config;
-
-
-import io.gaia_app.settings.bo.Settings;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-class DockerConfigTest {
-
- @Test
- void dockerClient_shouldUseDockerDaemonUrlSetting() {
- var settings = new Settings();
- settings.setDockerDaemonUrl("tcp://test:2375");
-
- var dockerConfig = new DockerConfig();
-
- var dockerClientConfig = dockerConfig.dockerClientConfig(settings);
-
- assertEquals("tcp://test:2375", dockerClientConfig.getDockerHost().toString());
- }
-
-}
diff --git a/src/test/java/io/gaia_app/stacks/bo/StepTest.java b/src/test/java/io/gaia_app/stacks/bo/StepTest.java
index ffce40db9..2db01014f 100644
--- a/src/test/java/io/gaia_app/stacks/bo/StepTest.java
+++ b/src/test/java/io/gaia_app/stacks/bo/StepTest.java
@@ -11,19 +11,6 @@
class StepTest {
- @Test
- void getLogs_shouldReturnOutputStreamResult() {
- var step = new Step();
-
- PrintWriter printWriter = new PrintWriter(step.getLogsWriter());
- printWriter.println("Test Line 1");
- printWriter.println("Test Line 2");
-
- var logs = step.getLogs();
-
- assertEquals("Test Line 1\nTest Line 2\n", logs);
- }
-
@Test
void start_shouldSetStatusToStarted() {
var step = new Step();
diff --git a/src/test/java/io/gaia_app/stacks/controller/JobRestControllerTest.kt b/src/test/java/io/gaia_app/stacks/controller/JobRestControllerTest.kt
index 0be455591..bf0154239 100644
--- a/src/test/java/io/gaia_app/stacks/controller/JobRestControllerTest.kt
+++ b/src/test/java/io/gaia_app/stacks/controller/JobRestControllerTest.kt
@@ -1,9 +1,8 @@
package io.gaia_app.stacks.controller
import io.gaia_app.modules.bo.TerraformModule
-import io.gaia_app.modules.repository.TerraformModuleRepository
-import io.gaia_app.runner.StackRunner
import io.gaia_app.stacks.bo.Job
+import io.gaia_app.stacks.bo.JobStatus
import io.gaia_app.stacks.bo.Stack
import io.gaia_app.stacks.repository.JobRepository
import io.gaia_app.stacks.repository.StackRepository
@@ -32,12 +31,6 @@ class JobRestControllerTest {
@Mock
lateinit var stepRepository: StepRepository
- @Mock
- lateinit var stackRepository: StackRepository
-
- @Mock
- lateinit var stackRunner: StackRunner
-
@InjectMocks
lateinit var controller: JobRestController
@@ -51,31 +44,15 @@ class JobRestControllerTest {
}
@Test
- fun `job() should return the running job if exists`() {
- // given
- val job = mock(Job::class.java)
-
- // when
- whenever(stackRunner.getJob(any())).thenReturn(of(job))
- controller.job("12")
-
- // then
- verify(stackRunner).getJob("12")
- verifyNoInteractions(jobRepository)
- }
-
- @Test
- fun `job() should return the saved job if no running`() {
+ fun `job() should return the job`() {
// given
val job = mock(Job::class.java)
// when
- whenever(stackRunner.getJob(any())).thenReturn(empty())
whenever(jobRepository.findById("12")).thenReturn(of(job))
controller.job("12")
// then
- verify(stackRunner, times(1)).getJob("12")
verify(jobRepository).findById("12")
}
@@ -98,33 +75,19 @@ class JobRestControllerTest {
assertThrows(JobNotFoundException::class.java) { controller.plan("test_jobId") }
}
- @Test
- fun `plan() should throw an exception for non existing stack`() {
- // when
- whenever(jobRepository.findById(any())).thenReturn(of(Job()))
- whenever(stackRepository.findById(any())).thenReturn(empty())
-
- // then
- assertThrows(NoSuchElementException::class.java) { controller.plan("test_jobId") }
- }
-
@Test
fun `plan() should plan a job`() {
// given
val job = Job()
- val stack = Stack()
- stack.module = TerraformModule()
+ job.status = JobStatus.PLAN_PENDING
// when
whenever(jobRepository.findById(any())).thenReturn(of(job))
- whenever(stackRepository.findById(any())).thenReturn(of(stack))
controller.plan("test_jobId")
// then
- val captor = forClass(JobWorkflow::class.java)
- verify(stackRunner).plan(captor.capture(), eq(stack.module), eq(stack))
- assertThat(captor.value).isNotNull
- assertThat(captor.value.job).isNotNull.isEqualTo(job)
+ assertThat(job.status).isEqualTo(JobStatus.PLAN_STARTED)
+ verify(jobRepository).save(job)
}
@Test
@@ -136,33 +99,19 @@ class JobRestControllerTest {
assertThrows(JobNotFoundException::class.java) { controller.apply("test_jobId") }
}
- @Test
- fun `apply() should throw an exception for non existing stack`() {
- // when
- whenever(jobRepository.findById(any())).thenReturn(of(Job()))
- whenever(stackRepository.findById(any())).thenReturn(empty())
-
- // then
- assertThrows(NoSuchElementException::class.java) { controller.apply("test_jobId") }
- }
-
@Test
fun `apply() should apply a job`() {
// given
val job = Job()
- val stack = Stack()
- stack.module = TerraformModule()
+ job.status = JobStatus.APPLY_PENDING
// when
whenever(jobRepository.findById(any())).thenReturn(of(job))
- whenever(stackRepository.findById(any())).thenReturn(of(stack))
controller.apply("test_jobId")
// then
- val captor = forClass(JobWorkflow::class.java)
- verify(stackRunner).apply(captor.capture(), eq(stack.module), eq(stack))
- assertThat(captor.value).isNotNull
- assertThat(captor.value.job).isNotNull.isEqualTo(job)
+ assertThat(job.status).isEqualTo(JobStatus.APPLY_STARTED)
+ verify(jobRepository).save(job)
}
@Test
@@ -174,33 +123,19 @@ class JobRestControllerTest {
assertThrows(JobNotFoundException::class.java) { controller.retry("test_jobId") }
}
- @Test
- fun `retry() should throw an exception for non existing stack`() {
- // when
- whenever(jobRepository.findById(any())).thenReturn(of(Job()))
- whenever(stackRepository.findById(any())).thenReturn(empty())
-
- // then
- assertThrows(NoSuchElementException::class.java) { controller.retry("test_jobId") }
- }
-
@Test
fun `retry() should retry a job`() {
// given
val job = Job()
- val stack = Stack()
- stack.module = TerraformModule()
+ job.status = JobStatus.PLAN_FAILED
// when
whenever(jobRepository.findById(any())).thenReturn(of(job))
- whenever(stackRepository.findById(any())).thenReturn(of(stack))
controller.retry("test_jobId")
// then
- val captor = forClass(JobWorkflow::class.java)
- verify(stackRunner).retry(captor.capture(), eq(stack.module), eq(stack))
- assertThat(captor.value).isNotNull
- assertThat(captor.value.job).isNotNull.isEqualTo(job)
+ assertThat(job.status).isEqualTo(JobStatus.PLAN_STARTED)
+ verify(jobRepository).save(job)
}
@Test
diff --git a/src/test/java/io/gaia_app/stacks/repository/StepRepositoryIT.java b/src/test/java/io/gaia_app/stacks/repository/StepRepositoryIT.java
index aa399d16b..9ebf6055a 100644
--- a/src/test/java/io/gaia_app/stacks/repository/StepRepositoryIT.java
+++ b/src/test/java/io/gaia_app/stacks/repository/StepRepositoryIT.java
@@ -10,6 +10,7 @@
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import java.io.IOException;
+import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -24,7 +25,7 @@ class StepRepositoryIT extends SharedMongoContainerTest {
void jobShouldBeSavedWithLogs() throws IOException {
var step = new Step(StepType.PLAN, "42");
step.setId("12");
- step.getLogsWriter().write("some logs");
+ step.setLogs(List.of("some logs"));
step.start();
step.end();