@@ -3,3 +3,4 @@ | |||
target/ | |||
testGit/ | |||
application-local.properties |
@@ -34,6 +34,7 @@ | |||
<spring-graalvm-native.version>0.8.0</spring-graalvm-native.version> | |||
<native-image-maven-plugin.version>20.2.0</native-image-maven-plugin.version> | |||
<jackson-datatype.version>2.11.2</jackson-datatype.version> | |||
<org.eclipse.jgit.version>5.9.0.202009080501-r</org.eclipse.jgit.version> | |||
</properties> | |||
<dependencies> | |||
@@ -60,10 +61,19 @@ | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-actuator</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.fasterxml.jackson.dataformat</groupId> | |||
<artifactId>jackson-dataformat-yaml</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.fasterxml.jackson.datatype</groupId> | |||
<artifactId>jackson-datatype-jsr310</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.eclipse.jgit</groupId> | |||
<artifactId>org.eclipse.jgit</artifactId> | |||
<version>${org.eclipse.jgit.version}</version> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
@@ -1,5 +1,6 @@ | |||
package tk.antoine_roux.wiki; | |||
import org.eclipse.jgit.api.errors.GitAPIException; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
@@ -9,17 +10,20 @@ import org.springframework.web.bind.annotation.*; | |||
import tk.antoine_roux.wiki.annotation.ApiPrefix; | |||
import tk.antoine_roux.wiki.annotation.ApiVersion; | |||
import tk.antoine_roux.wiki.configuration.Exception.DeleteRunnerException; | |||
import tk.antoine_roux.wiki.model.internal.HookEvent; | |||
import tk.antoine_roux.wiki.model.internal.Job; | |||
import tk.antoine_roux.wiki.model.internal.GitlabCI; | |||
import tk.antoine_roux.wiki.model.internal.Runner; | |||
import tk.antoine_roux.wiki.model.request.AddRunner; | |||
import tk.antoine_roux.wiki.model.request.HookEvent; | |||
import tk.antoine_roux.wiki.model.request.JobRequest; | |||
import tk.antoine_roux.wiki.model.request.TokenRunner; | |||
import tk.antoine_roux.wiki.model.response.JobResponse; | |||
import tk.antoine_roux.wiki.model.response.RegisterRunnerResponse; | |||
import tk.antoine_roux.wiki.service.GitService; | |||
import tk.antoine_roux.wiki.service.JobManager; | |||
import tk.antoine_roux.wiki.service.RunnerRegistrar; | |||
import tk.antoine_roux.wiki.utilitary.Boolean; | |||
import java.io.IOException; | |||
import java.lang.invoke.MethodHandles; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
@@ -35,11 +39,13 @@ public class ControllerHandlers { | |||
private final RunnerRegistrar runnerRegistrar; | |||
private final JobManager jobManager; | |||
private final GitService gitService; | |||
@Autowired | |||
public ControllerHandlers(RunnerRegistrar runnerRegistrar, JobManager jobManager) { | |||
public ControllerHandlers(RunnerRegistrar runnerRegistrar, JobManager jobManager, GitService gitService) { | |||
this.runnerRegistrar = runnerRegistrar; | |||
this.jobManager = jobManager; | |||
this.gitService = gitService; | |||
} | |||
/** | |||
@@ -102,8 +108,8 @@ public class ControllerHandlers { | |||
@ApiVersion(API_VERSION) | |||
@PostMapping("/jobs/request") | |||
public ResponseEntity<Job> jobRequest(@RequestBody JobRequest jobRequest) { | |||
Optional<Job> currentJob = this.jobManager.popJob(jobRequest); | |||
public ResponseEntity<JobResponse> jobRequest(@RequestBody JobRequest jobRequest) { | |||
Optional<JobResponse> currentJob = this.jobManager.popJob(jobRequest); | |||
return currentJob | |||
.map(job -> ResponseEntity.status(HttpStatus.CREATED).body(job)) | |||
.orElseGet(() -> ResponseEntity.noContent().build()); | |||
@@ -118,12 +124,13 @@ public class ControllerHandlers { | |||
@ApiVersion(API_VERSION) | |||
@PostMapping("/webhook") | |||
public ResponseEntity<Void> webhook(@RequestBody HookEvent webHookData) { | |||
public ResponseEntity<Void> webhook(@RequestBody HookEvent webHookData) throws IOException, GitAPIException { | |||
ResponseEntity.BodyBuilder responseEntity; | |||
Optional<Job> job = webHookData.toJob(); | |||
Optional<GitlabCI> gitlabCI = this.gitService.getYMLGitlabCI(webHookData); | |||
if (job.isPresent()) { | |||
this.jobManager.stackJob(job.get()); | |||
if (gitlabCI.isPresent()) { | |||
JobResponse job = gitlabCI.get().buildJobResponse(); | |||
this.jobManager.stackJob(job); | |||
responseEntity = ResponseEntity.ok(); | |||
} else { | |||
responseEntity = ResponseEntity.unprocessableEntity(); | |||
@@ -2,15 +2,18 @@ package tk.antoine_roux.wiki; | |||
import org.springframework.boot.SpringApplication; | |||
import org.springframework.boot.autoconfigure.SpringBootApplication; | |||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||
import org.springframework.web.servlet.function.RouterFunction; | |||
import org.springframework.web.servlet.function.RouterFunctions; | |||
import org.springframework.web.servlet.function.ServerResponse; | |||
import tk.antoine_roux.wiki.configuration.GitConfiguration; | |||
/** | |||
* Main class | |||
*/ | |||
// force spring application to not use glibc or any non jdk code which is bad for graalvm | |||
@SpringBootApplication(proxyBeanMethods = false) | |||
@EnableConfigurationProperties(GitConfiguration.GitlabCIContextProperties.class) | |||
public class MainLauncher { | |||
/** | |||
@@ -0,0 +1,50 @@ | |||
package tk.antoine_roux.wiki.configuration; | |||
import com.fasterxml.jackson.databind.ObjectMapper; | |||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | |||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; | |||
import org.springframework.boot.context.properties.ConfigurationProperties; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
import org.springframework.core.env.Environment; | |||
@Configuration(proxyBeanMethods = false) | |||
public class GitConfiguration { | |||
@Bean | |||
public YAMLFactory yamlFactory() { | |||
return YAMLFactory.builder().build(); | |||
} | |||
@Bean(name = "YAMLObjectMapper") | |||
public ObjectMapper ObjectMapper(YAMLFactory yamlFactory) { | |||
return new ObjectMapper(yamlFactory); | |||
} | |||
@Bean | |||
public UsernamePasswordCredentialsProvider credentialsProvider(GitlabCIContextProperties gitlabCIContextProperties, Environment environment) { | |||
return new UsernamePasswordCredentialsProvider(gitlabCIContextProperties.getUsername(), gitlabCIContextProperties.getPassword()); | |||
} | |||
@ConfigurationProperties("gitlab-ci") | |||
public static class GitlabCIContextProperties { | |||
private String username; | |||
private String password; | |||
public String getUsername() { | |||
return this.username; | |||
} | |||
public void setUsername(String username) { | |||
this.username = username; | |||
} | |||
public String getPassword() { | |||
return this.password; | |||
} | |||
public void setPassword(String password) { | |||
this.password = password; | |||
} | |||
} | |||
} |
@@ -1,7 +1,13 @@ | |||
package tk.antoine_roux.wiki.configuration; | |||
import com.fasterxml.jackson.databind.ObjectMapper; | |||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | |||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
import org.springframework.context.annotation.Primary; | |||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; | |||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; | |||
import tk.antoine_roux.wiki.utilitary.Constant; | |||
@@ -17,4 +23,18 @@ public class WebConfiguration implements WebMvcRegistrations { | |||
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { | |||
return new ApiVersionRequestMappingHandlerMapping(Constant.VERSION_PREFIX); | |||
} | |||
/** | |||
* build default Spring boot {@link ObjectMapper} | |||
* this bean avoid spring boot to auto detect {@link GitConfiguration#ObjectMapper(YAMLFactory)} | |||
* as default {@link ObjectMapper} | |||
* | |||
* @param jackson2ObjectMapperBuilder | |||
* @return | |||
*/ | |||
@Bean | |||
@Primary | |||
public ObjectMapper jackson2ObjectMapperBuilder(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) { | |||
return jackson2ObjectMapperBuilder.build(); | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
import com.fasterxml.jackson.annotation.JsonAnySetter; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import tk.antoine_roux.wiki.model.response.JobResponse; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import java.util.Map; | |||
public class GitlabCI { | |||
public String image; | |||
@JsonProperty("before_script") | |||
public List<String> beforeScript = Collections.emptyList(); | |||
@JsonProperty("after_script") | |||
public List<String> afterScript = Collections.emptyList(); | |||
public Map<String, Job> jobs = Collections.emptyMap(); | |||
@JsonAnySetter | |||
public void setJobs(String key, Job value) { | |||
this.jobs.put(key, value); | |||
} | |||
public JobResponse buildJobResponse() { | |||
return null; | |||
} | |||
public enum RuleEnum { | |||
IF("if"); | |||
String content; | |||
RuleEnum(String s) { | |||
this.content = s; | |||
} | |||
} | |||
private static class Job { | |||
public String stage; | |||
public String image; | |||
public Map<String, String> variables = Collections.emptyMap(); | |||
public List<String> services = Collections.emptyList(); | |||
public Map<RuleEnum, String> rules = Collections.emptyMap(); | |||
public List<String> script = Collections.emptyList(); | |||
} | |||
} |
@@ -1,56 +0,0 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import java.time.ZonedDateTime; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import java.util.UUID; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
public class HookEvent { | |||
public String secret; | |||
public String ref; | |||
public String before; | |||
public String after; | |||
@JsonProperty("compare_url") | |||
public String compareUrl; | |||
public List<Commit> commits; | |||
@JsonProperty("head_commit") | |||
public String headCommit; | |||
public Repository repository; | |||
public User pusher; | |||
public User sender; | |||
AtomicInteger idIncrementer = new AtomicInteger(); | |||
/** | |||
* convert {@link HookEvent} to {@link Job} if possible | |||
* else return and {@link Optional#empty()} | |||
*/ | |||
public Optional<Job> toJob() { | |||
Optional<Job> optJob; | |||
if (this.commits.isEmpty()) { | |||
optJob = Optional.empty(); | |||
} else { | |||
// search for head commit or take first in event's list of commit | |||
Commit commit = this.commits.stream().filter(co -> co.id.equals(this.headCommit)) | |||
.findFirst().orElse(this.commits.get(0)); | |||
Job.Commit co = new Job.Commit( | |||
commit.author.email, commit.author.name, commit.timestamp, | |||
commit.id, commit.message, commit.id.substring(0, 8), commit.message | |||
); | |||
Job job = new Job( | |||
null, co, null, ZonedDateTime.now(), null, | |||
this.idIncrementer.getAndIncrement(), UUID.randomUUID().toString(), this.ref, | |||
null, "root", null, JobStatus.CREATED, false, this.pusher.toReducedUser() | |||
); | |||
optJob = Optional.of(job); | |||
} | |||
return optJob; | |||
} | |||
} |
@@ -1,93 +0,0 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
import com.fasterxml.jackson.annotation.JsonFormat; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import java.time.ZonedDateTime; | |||
// ID int `json:"id"` | |||
// Token string `json:"token"` | |||
// AllowGitFetch bool `json:"allow_git_fetch"` | |||
// JobInfo JobInfo `json:"job_info"` | |||
// GitInfo GitInfo `json:"git_info"` | |||
// RunnerInfo RunnerInfo `json:"runner_info"` | |||
// Variables JobVariables `json:"variables"` | |||
// Steps Steps `json:"steps"` | |||
// Image Image `json:"image"` | |||
// Services Services `json:"services"` | |||
// Artifacts Artifacts `json:"artifacts"` | |||
// Cache Caches `json:"cache"` | |||
// Credentials []Credentials `json:"credentials"` | |||
// Dependencies Dependencies `json:"dependencies"` | |||
// Features GitlabFeatures `json:"features"` | |||
// Secrets Secrets `json:"secrets,omitempty"` | |||
public class Job { | |||
@JsonProperty("artifacts_file") | |||
public String artifactsFile; | |||
public Commit commit; | |||
public String coverage; | |||
@JsonProperty("created_at") | |||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssSSSXXX") | |||
public ZonedDateTime createdAt; | |||
@JsonProperty("finished_at") | |||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssSSSXXX") | |||
public ZonedDateTime finishedAt; | |||
public Integer id; | |||
public String name; | |||
public String ref; | |||
public Runner runner; | |||
public String stage; | |||
@JsonProperty("started_at") | |||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssSSSXXX") | |||
public ZonedDateTime startedAt; | |||
public JobStatus status; | |||
public boolean tag; | |||
public UserReduced user; | |||
public Job( | |||
String artifactsFile, Commit commit, String coverage, ZonedDateTime createdAt, ZonedDateTime finishedAt, | |||
Integer id, String name, String ref, Runner runner, String stage, ZonedDateTime startedAt, JobStatus status, | |||
boolean tag, UserReduced user | |||
) { | |||
this.artifactsFile = artifactsFile; | |||
this.commit = commit; | |||
this.coverage = coverage; | |||
this.createdAt = createdAt; | |||
this.finishedAt = finishedAt; | |||
this.id = id; | |||
this.name = name; | |||
this.ref = ref; | |||
this.runner = runner; | |||
this.stage = stage; | |||
this.startedAt = startedAt; | |||
this.status = status; | |||
this.tag = tag; | |||
this.user = user; | |||
} | |||
public static class Commit { | |||
@JsonProperty("author_email") | |||
public String authorEmail; | |||
@JsonProperty("author_name") | |||
public String authorName; | |||
@JsonProperty("created_at") | |||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssSSSXXX") | |||
public ZonedDateTime createdAt; | |||
public String id; | |||
public String message; | |||
@JsonProperty("short_id") | |||
public String shortId; | |||
public String title; | |||
public Commit(String authorEmail, String authorName, ZonedDateTime createdAt, String id, String message, String shortId, String title) { | |||
this.authorEmail = authorEmail; | |||
this.authorName = authorName; | |||
this.createdAt = createdAt; | |||
this.id = id; | |||
this.message = message; | |||
this.shortId = shortId; | |||
this.title = title; | |||
} | |||
} | |||
} |
@@ -1,12 +0,0 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
/** | |||
* This enum represent all state a job could take during | |||
* his lifecycle | |||
*/ | |||
public enum JobStatus { | |||
CREATED, | |||
STARTED, | |||
STOPPED, | |||
FINISHED | |||
} |
@@ -1,6 +1,7 @@ | |||
package tk.antoine_roux.wiki.model.request; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import tk.antoine_roux.wiki.model.request.secondary.RunnerInfo; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
@@ -0,0 +1,55 @@ | |||
package tk.antoine_roux.wiki.model.request; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import tk.antoine_roux.wiki.model.request.secondary.Commit; | |||
import tk.antoine_roux.wiki.model.request.secondary.Repository; | |||
import tk.antoine_roux.wiki.model.request.secondary.User; | |||
import java.util.List; | |||
public class HookEvent { | |||
public String secret; | |||
public String ref; | |||
public String before; | |||
public String after; | |||
@JsonProperty("compare_url") | |||
public String compareUrl; | |||
public List<Commit> commits; | |||
@JsonProperty("head_commit") | |||
public String headCommit; | |||
public Repository repository; | |||
public User pusher; | |||
public User sender; | |||
// AtomicInteger idIncrementer = new AtomicInteger(); | |||
// | |||
// /** | |||
// * convert {@link HookEvent} to {@link Job} if possible | |||
// * else return and {@link Optional#empty()} | |||
// */ | |||
// public Optional<Job> toJob() { | |||
// Optional<Job> optJob; | |||
// | |||
// if (this.commits.isEmpty()) { | |||
// optJob = Optional.empty(); | |||
// } else { | |||
// // search for head commit or take first in event's list of commit | |||
// Commit commit = this.commits.stream().filter(co -> co.id.equals(this.headCommit)) | |||
// .findFirst().orElse(this.commits.get(0)); | |||
// | |||
// Job.Commit co = new Job.Commit( | |||
// commit.author.email, commit.author.name, commit.timestamp, | |||
// commit.id, commit.message, commit.id.substring(0, 8), commit.message | |||
// ); | |||
// | |||
// Job job = new Job( | |||
// null, co, null, ZonedDateTime.now(), null, | |||
// this.idIncrementer.getAndIncrement(), UUID.randomUUID().toString(), this.ref, | |||
// null, "root", null, JobStatus.CREATED, false, this.pusher.toReducedUser() | |||
// ); | |||
// optJob = Optional.of(job); | |||
// } | |||
// | |||
// return optJob; | |||
// } | |||
} |
@@ -1,5 +1,7 @@ | |||
package tk.antoine_roux.wiki.model.request; | |||
import tk.antoine_roux.wiki.model.request.secondary.RunnerInfo; | |||
/** | |||
* Job Request compose from {@link RunnerInfo} and token field | |||
*/ | |||
@@ -1,4 +1,4 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
package tk.antoine_roux.wiki.model.request.secondary; | |||
import com.fasterxml.jackson.annotation.JsonFormat; | |||
@@ -13,7 +13,7 @@ public class Commit { | |||
public UserReduced committer; | |||
public String verification; | |||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX") | |||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX") | |||
public ZonedDateTime timestamp; | |||
public List<String> added; |
@@ -1,4 +1,4 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
package tk.antoine_roux.wiki.model.request.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
@@ -1,4 +1,4 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
package tk.antoine_roux.wiki.model.request.secondary; | |||
/** | |||
* model about repository permission |
@@ -1,4 +1,4 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
package tk.antoine_roux.wiki.model.request.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
@@ -1,4 +1,4 @@ | |||
package tk.antoine_roux.wiki.model.request; | |||
package tk.antoine_roux.wiki.model.request.secondary; | |||
import java.util.Map; | |||
@@ -1,4 +1,4 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
package tk.antoine_roux.wiki.model.request.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
@@ -1,4 +1,4 @@ | |||
package tk.antoine_roux.wiki.model.internal; | |||
package tk.antoine_roux.wiki.model.request.secondary; | |||
/** | |||
* Reduced user information |
@@ -0,0 +1,33 @@ | |||
package tk.antoine_roux.wiki.model.response; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import tk.antoine_roux.wiki.model.response.secondary.*; | |||
import java.util.List; | |||
import java.util.Map; | |||
/** | |||
* job format use to submit | |||
* information to gitlab-runner | |||
*/ | |||
public class JobResponse { | |||
public Integer id; | |||
public String token; | |||
@JsonProperty("allow_git_fetch") | |||
public boolean allowGitFetch; | |||
@JsonProperty("job_info") | |||
public JobInfo jobInfo; | |||
@JsonProperty("git_info") | |||
public GitInfo gitInfo; | |||
public RunnerInfo runnerInfo; | |||
public List<JobVariable> variables; | |||
public List<Step> steps; | |||
public Image image; | |||
public List<Image> services; | |||
public List<Artifact> artifacts; | |||
public List<Cache> cache; | |||
public List<Credential> credentials; | |||
public List<Dependency> dependencies; | |||
public GitLabFeatures features; | |||
public Map<String, Secret> secrets; | |||
} |
@@ -0,0 +1,19 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import java.util.List; | |||
public class Artifact { | |||
public String name; | |||
public boolean untracked; | |||
public List<String> paths; | |||
public List<String> exclude; | |||
public String when; | |||
@JsonProperty("artifact_type") | |||
public String artifactType; | |||
@JsonProperty("artifact_format") | |||
public String artifactFormat; | |||
@JsonProperty("expire_in") | |||
public String expireIn; | |||
} |
@@ -0,0 +1,10 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import java.util.List; | |||
public class Cache { | |||
public String key; | |||
public boolean untracked; | |||
public String policy; | |||
public List<String> paths; | |||
} |
@@ -0,0 +1,8 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
public class Credential { | |||
public String type; | |||
public String url; | |||
public String username; | |||
public String password; | |||
} |
@@ -0,0 +1,16 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
public class Dependency { | |||
public int id; | |||
public String token; | |||
public String name; | |||
@JsonProperty("artifacts_file") | |||
public DependencyArtifactsFile artifactsFile; | |||
public static class DependencyArtifactsFile { | |||
public String filename; | |||
public int size; | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
public class GitInfo { | |||
@JsonProperty("repo_url") | |||
public String repoURL; | |||
public String ref; | |||
public String sha; | |||
@JsonProperty("before_sha") | |||
public String beforeSha; | |||
@JsonProperty("ref_type") | |||
public String refType; | |||
public String[] refspecs; | |||
public int depth; | |||
} |
@@ -0,0 +1,8 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
public class GitLabFeatures { | |||
@JsonProperty("trace_sections") | |||
public boolean traceSections; | |||
} |
@@ -0,0 +1,26 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import com.fasterxml.jackson.annotation.JsonInclude; | |||
import java.util.List; | |||
public class Image { | |||
public String name; | |||
@JsonInclude(JsonInclude.Include.NON_NULL) | |||
public String alias; | |||
@JsonInclude(JsonInclude.Include.NON_NULL) | |||
public List<String> command; | |||
@JsonInclude(JsonInclude.Include.NON_NULL) | |||
public List<String> entrypoint; | |||
@JsonInclude(JsonInclude.Include.NON_NULL) | |||
public Port ports; | |||
static class Port { | |||
@JsonInclude(JsonInclude.Include.NON_NULL) | |||
public String name; | |||
@JsonInclude(JsonInclude.Include.NON_NULL) | |||
public int number; | |||
@JsonInclude(JsonInclude.Include.NON_NULL) | |||
public String protocol; | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
public class JobInfo { | |||
public String name; | |||
public String stage; | |||
@JsonProperty("project_id") | |||
public String projectID; | |||
@JsonProperty("project_name") | |||
public String projectName; | |||
} |
@@ -0,0 +1,15 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
public class JobVariable { | |||
public String key; | |||
public String value; | |||
@JsonProperty("public") | |||
public boolean isPublic; | |||
@JsonProperty("-") | |||
public boolean internal; | |||
public boolean file; | |||
public boolean masked; | |||
public boolean raw; | |||
} |
@@ -0,0 +1,5 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
public class RunnerInfo { | |||
public int timeout; | |||
} |
@@ -0,0 +1,33 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import com.fasterxml.jackson.annotation.JsonInclude; | |||
import java.util.Map; | |||
public class Secret { | |||
@JsonInclude(JsonInclude.Include.NON_EMPTY) | |||
public VaultSecret vault; | |||
public static class VaultSecret { | |||
public VaultServer server; | |||
public VaultEngine engine; | |||
public String path; | |||
public String field; | |||
} | |||
public static class VaultServer { | |||
public String url; | |||
public VaultAuth auth; | |||
} | |||
public static class VaultAuth { | |||
public String name; | |||
public String path; | |||
public Map<String, Object> data; | |||
} | |||
public static class VaultEngine { | |||
public String name; | |||
public String path; | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
package tk.antoine_roux.wiki.model.response.secondary; | |||
import com.fasterxml.jackson.annotation.JsonProperty; | |||
import java.util.List; | |||
public class Step { | |||
public String name; | |||
public List<String> script; | |||
public int timeout; | |||
public String when; | |||
@JsonProperty("allow_failure") | |||
public boolean allowFailure; | |||
} |
@@ -0,0 +1,75 @@ | |||
package tk.antoine_roux.wiki.service; | |||
import com.fasterxml.jackson.databind.ObjectMapper; | |||
import org.eclipse.jgit.api.Git; | |||
import org.eclipse.jgit.api.errors.GitAPIException; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.revwalk.RevWalk; | |||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; | |||
import org.eclipse.jgit.treewalk.TreeWalk; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.beans.factory.annotation.Qualifier; | |||
import org.springframework.stereotype.Service; | |||
import tk.antoine_roux.wiki.model.internal.GitlabCI; | |||
import tk.antoine_roux.wiki.model.request.HookEvent; | |||
import tk.antoine_roux.wiki.model.request.secondary.Commit; | |||
import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.util.Optional; | |||
@Service | |||
public class GitService { | |||
public static final String GITLAB_CI_FILE_PATH = ".gitlab-ci.yml"; | |||
private static final String GITLAB_RUNNER_CLONE_PREFIX = "gitlab-runner-clone"; | |||
public final UsernamePasswordCredentialsProvider credentialsProvider; | |||
private final ObjectMapper objectMapper; | |||
@Autowired | |||
public GitService(UsernamePasswordCredentialsProvider credentialsProvider, @Qualifier("YAMLObjectMapper") ObjectMapper objectMapper) { | |||
this.credentialsProvider = credentialsProvider; | |||
this.objectMapper = objectMapper; | |||
} | |||
/** | |||
* return .gitlab-ci.yml content for {@link HookEvent} | |||
*/ | |||
public Optional<GitlabCI> getYMLGitlabCI(HookEvent hookEvent) throws IOException, GitAPIException { | |||
Optional<GitlabCI> optJob; | |||
if (hookEvent.commits.isEmpty()) { | |||
optJob = Optional.empty(); | |||
} else { | |||
// search for head commit or take first in event's list of commit | |||
Commit commit = hookEvent.commits.stream().filter(co -> co.id.equals(hookEvent.headCommit)) | |||
.findFirst().orElse(hookEvent.commits.get(0)); | |||
optJob = Optional.of(this.getGitlabCIContent(hookEvent.repository.cloneUrl, commit)); | |||
} | |||
return optJob; | |||
} | |||
/** | |||
* return .gitlab-ci.yml content for given commit into cloneUrl repository | |||
*/ | |||
private GitlabCI getGitlabCIContent(String cloneURL, Commit commit) throws IOException, GitAPIException { | |||
Git call = Git.cloneRepository() | |||
.setURI(cloneURL) | |||
.setCredentialsProvider(this.credentialsProvider) | |||
.setDirectory(Files.createTempDirectory(GITLAB_RUNNER_CLONE_PREFIX).toFile()) | |||
.call(); | |||
RevWalk revWalk = new RevWalk(call.getRepository()); | |||
RevCommit revCommit = revWalk.parseCommit(ObjectId.fromString(commit.id)); | |||
try (TreeWalk walk = TreeWalk.forPath(call.getRepository(), GITLAB_CI_FILE_PATH, revCommit.getTree())) { | |||
if (walk != null) { | |||
byte[] bytes = call.getRepository().open(walk.getObjectId(0)).getBytes(); | |||
return this.objectMapper.readValue(bytes, GitlabCI.class); | |||
} else { | |||
throw new IllegalArgumentException("No path found."); | |||
} | |||
} | |||
} | |||
} |
@@ -1,9 +1,9 @@ | |||
package tk.antoine_roux.wiki.service; | |||
import org.springframework.stereotype.Service; | |||
import tk.antoine_roux.wiki.model.internal.HookEvent; | |||
import tk.antoine_roux.wiki.model.internal.Job; | |||
import tk.antoine_roux.wiki.model.request.JobRequest; | |||
import tk.antoine_roux.wiki.model.request.HookEvent; | |||
import tk.antoine_roux.wiki.model.response.JobResponse; | |||
import java.util.Optional; | |||
import java.util.concurrent.ConcurrentLinkedQueue; | |||
@@ -12,16 +12,16 @@ import java.util.concurrent.ConcurrentLinkedQueue; | |||
public class JobManager { | |||
/** | |||
* concurrent list of {@link Job} fill by {@link tk.antoine_roux.wiki.ControllerHandlers#webhook(HookEvent)} | |||
* concurrent list of {@link JobResponse} fill by {@link tk.antoine_roux.wiki.ControllerHandlers#webhook(HookEvent)} | |||
* and pop by {@link tk.antoine_roux.wiki.ControllerHandlers#jobRequest(JobRequest)} | |||
*/ | |||
ConcurrentLinkedQueue<Job> jobQueue = new ConcurrentLinkedQueue<>(); | |||
ConcurrentLinkedQueue<JobResponse> jobQueue = new ConcurrentLinkedQueue<>(); | |||
public void stackJob(Job newJob) { | |||
public void stackJob(JobResponse newJob) { | |||
this.jobQueue.add(newJob); | |||
} | |||
public Optional<Job> popJob(JobRequest jobRequest) { | |||
public Optional<JobResponse> popJob(JobRequest jobRequest) { | |||
return Optional.ofNullable(this.jobQueue.poll()); | |||
} | |||
} |
@@ -1,6 +1,7 @@ | |||
spring.main.banner-mode=off | |||
spring.main.lazy-initialization=false | |||
spring.output.ansi.enabled=ALWAYS | |||
spring.jackson.serialization.write-dates-as-timestamps=false | |||
server.port=^application.port^ | |||
@@ -17,3 +18,6 @@ info.name=gitlab-runner-gateway | |||
info.more.detail=This is a REST API use to gateway gitlab runner call to gitlab instance | |||
management.endpoints.web.exposure.include=mappings | |||
gitlab-ci.username= | |||
gitlab-ci.password= |