Skip to content

Commit

Permalink
✨ : add stack instanciations
Browse files Browse the repository at this point in the history
  • Loading branch information
juwit committed Jun 4, 2019
1 parent 0e8a14d commit f6fd8d5
Show file tree
Hide file tree
Showing 13 changed files with 648 additions and 14 deletions.
13 changes: 10 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,20 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.2</version>
</dependency>


<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/codeka/gaia/Gaia.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ CommandLineRunner cli(TerraformModuleRepository repository,
StackRepository stackRepository){
return args -> {
repository.deleteAll();
terraformStateRepository.deleteAll();
//terraformStateRepository.deleteAll();
stackRepository.deleteAll();

// create dummy module for tests
Expand Down Expand Up @@ -61,6 +61,7 @@ CommandLineRunner cli(TerraformModuleRepository repository,
stack.setId("5a215b6b-fe53-4afa-85f0-a10175a7f264");
stack.setName("mongo-instance-1");
stack.setModuleId("e01f9925-a559-45a2-8a55-f93dc434c676");
stack.getVariableValues().put("mongo_container_name", "test");
stack.getVariableValues().put("mongo_exposed_port", "27117");
stack.setProviderSpec("provider \"poulp\" {\n" +
" host = \"unix:///var/run/docker.sock\"\n" +
Expand Down
60 changes: 60 additions & 0 deletions src/main/java/io/codeka/gaia/bo/Job.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.codeka.gaia.bo;

import com.fasterxml.jackson.annotation.JsonIgnore;

import java.io.StringWriter;
import java.io.Writer;

/**
* A job is the instanciation of a stack
*/
public class Job {

private String id;

private StringWriter stringWriter = new StringWriter();

private String logs;

private JobStatus jobStatus;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getLogs() {
if(jobStatus == JobStatus.FINISHED){
return logs;
}
return stringWriter.toString();
}

@JsonIgnore
public Writer getLogsWriter(){
return stringWriter;
}

public JobStatus getStatus(){
return this.jobStatus;
}

public void start() {
this.jobStatus = JobStatus.RUNNING;
}

public void end(){
this.jobStatus = JobStatus.FINISHED;
// getting final logs
this.logs = this.stringWriter.toString();
}

public void fail() {
this.jobStatus = JobStatus.FAILED;
// getting final logs
this.logs = this.stringWriter.toString();
}
}
5 changes: 5 additions & 0 deletions src/main/java/io/codeka/gaia/bo/JobStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.codeka.gaia.bo;

public enum JobStatus {
RUNNING, FINISHED, FAILED
}
9 changes: 9 additions & 0 deletions src/main/java/io/codeka/gaia/config/AsyncConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.codeka.gaia.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@Configuration
public class AsyncConfig {
}
49 changes: 43 additions & 6 deletions src/main/java/io/codeka/gaia/controller/StackController.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
package io.codeka.gaia.controller;

import io.codeka.gaia.bo.Job;
import io.codeka.gaia.repository.StackRepository;
import io.codeka.gaia.repository.TerraformModuleRepository;
import io.codeka.gaia.runner.StackRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.UUID;


@Controller
public class StackController {

private TerraformModuleRepository terraformModuleRepository;

private StackRepository stackRepository;

private Logger log = LoggerFactory.getLogger(StackController.class);
private StackRunner stackRunner;

private TerraformModuleRepository terraformModuleRepository;

@Autowired
public StackController(TerraformModuleRepository terraformModuleRepository, StackRepository stackRepository) {
this.terraformModuleRepository = terraformModuleRepository;
public StackController(StackRepository stackRepository, StackRunner stackRunner, TerraformModuleRepository terraformModuleRepository) {
this.stackRepository = stackRepository;
this.stackRunner = stackRunner;
this.terraformModuleRepository = terraformModuleRepository;
}

@GetMapping("/stacks")
Expand All @@ -42,4 +49,34 @@ public String editStack(@PathVariable String stackId, Model model){
return "stack";
}

@GetMapping("/stacks/{stackId}/apply")
public String applyStack(@PathVariable String stackId, Model model){
// checking if the stack exists
// TODO throw an exception (404) if not
if(stackRepository.existsById(stackId)){
model.addAttribute("stackId", stackId);
}

// create a new job
var job = new Job();
job.setId(UUID.randomUUID().toString());

model.addAttribute("jobId", job.getId());

// get the stack
var stack = this.stackRepository.findById(stackId).get();
// get the module
var module = this.terraformModuleRepository.findById(stack.getModuleId()).get();

this.stackRunner.run(job, module, stack);

return "stack_apply";
}

@GetMapping("/api/stacks/{stackId}/jobs/{jobId}")
@ResponseBody
public Job getJob(@PathVariable String stackId, @PathVariable String jobId){
return this.stackRunner.getJob(jobId);
}

}
114 changes: 114 additions & 0 deletions src/main/java/io/codeka/gaia/runner/HttpHijackWorkaround.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package io.codeka.gaia.runner;

import com.spotify.docker.client.LogReader;
import com.spotify.docker.client.LogStream;
import jnr.unixsocket.UnixSocketChannel;
import org.apache.http.conn.EofSensorInputStream;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.io.IdentityInputStream;
import org.apache.http.impl.io.SessionInputBufferImpl;
import org.glassfish.jersey.message.internal.EntityInputStream;

import java.io.FilterInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.Socket;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.LinkedList;
import java.util.List;

public class HttpHijackWorkaround {
public static WritableByteChannel getOutputStream(final LogStream stream, final String uri) throws Exception {
// @formatter:off
final String[] fields =
new String[] {"reader",
"stream",
"original",
"input",
"in",
"in",
"in",
"eofWatcher",
"wrappedEntity",
"content",
"in",
"inStream"};

final String[] containingClasses =
new String[] {"com.spotify.docker.client.DefaultLogStream",
LogReader.class.getName(),
"org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream",
EntityInputStream.class.getName(),
FilterInputStream.class.getName(),
FilterInputStream.class.getName(),
FilterInputStream.class.getName(),
EofSensorInputStream.class.getName(),
HttpEntityWrapper.class.getName(),
BasicHttpEntity.class.getName(),
IdentityInputStream.class.getName(),
SessionInputBufferImpl.class.getName()};
// @formatter:on

final List<String[]> fieldClassTuples = new LinkedList<>();
for (int i = 0; i < fields.length; i++) {
fieldClassTuples.add(new String[] {containingClasses[i], fields[i]});
}

if (uri.startsWith("unix:")) {
//fieldClassTuples.add(new String[] {"org.apache.http.impl.conn.LoggingInputStream", "in"});
fieldClassTuples.add(new String[] {"sun.nio.ch.ChannelInputStream", "ch"});
} else if (uri.startsWith("https:")) {
final float jvmVersion = Float.parseFloat(System.getProperty("java.specification.version"));
fieldClassTuples.add(new String[] {"sun.security.ssl.AppInputStream", jvmVersion < 1.9f ? "c" : "socket"});
} else {
fieldClassTuples.add(new String[] {"java.net.SocketInputStream", "socket"});
}

final Object res = getInternalField(stream, fieldClassTuples);
if (res instanceof WritableByteChannel) {
return (WritableByteChannel) res;
} else if (res instanceof Socket) {
return Channels.newChannel(((Socket) res).getOutputStream());
} else {
throw new AssertionError("Expected " + WritableByteChannel.class.getName() + " or " + Socket.class.getName() + " but found: " +
res.getClass().getName());
}
}

public static InputStream getInputStream(LogStream stream) {
final String[] fields = new String[] { "reader", "stream" }; //$NON-NLS-1$ //$NON-NLS-2$
final String[] declared = new String[] { "com.spotify.docker.client.DefaultLogStream", LogReader.class.getName()};

List<String[]> list = new LinkedList<>();
for (int i = 0; i < fields.length; i++) {
list.add(new String[] { declared[i], fields[i] });
}
return (InputStream) getInternalField(stream, list);
}

/**
* Recursively traverse a hierarchy of fields in classes, obtain their value and continue the traversing on the optained object
*
* @param fieldContent current object to operate on
* @param classFieldTupels the class/field hierarchy
*
* @return the content of the leaf in the traversed hierarchy path
*/
private static Object getInternalField(final Object fieldContent, final List<String[]> classFieldTupels) {
Object curr = fieldContent;
for (final String[] classFieldTuple : classFieldTupels) {
//noinspection ConstantConditions
final Field field;
try {
field = Class.forName(classFieldTuple[0]).getDeclaredField(classFieldTuple[1]);
field.setAccessible(true);
curr = field.get(curr);
} catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
}
return curr;
}
}
Loading

0 comments on commit f6fd8d5

Please sign in to comment.