From 6b65fa8e200b9f8e26810834fef4633930218666 Mon Sep 17 00:00:00 2001 From: Antoine Date: Thu, 10 Sep 2020 20:03:50 +0200 Subject: [PATCH] add spring actuator, and api version and prefix support --- doc-gitlab-runner.md | 10 ++ pom.xml | 4 + .../java/tk/antoine_roux/wiki/Constant.java | 11 +++ .../antoine_roux/wiki/ControllerHandlers.java | 58 ++++++++++++ .../tk/antoine_roux/wiki/MainLauncher.java | 13 +-- .../tk/antoine_roux/wiki/RunnerRegistrar.java | 41 +++++++++ .../wiki/annotation/ApiPrefix.java | 12 +++ .../wiki/annotation/ApiVersion.java | 12 +++ ...piVersionRequestMappingHandlerMapping.java | 91 +++++++++++++++++++ .../wiki/configuration/WebConfiguration.java | 25 +++++ src/main/resources/application.properties | 12 ++- 11 files changed, 278 insertions(+), 11 deletions(-) create mode 100644 doc-gitlab-runner.md create mode 100644 src/main/java/tk/antoine_roux/wiki/Constant.java create mode 100644 src/main/java/tk/antoine_roux/wiki/ControllerHandlers.java create mode 100644 src/main/java/tk/antoine_roux/wiki/RunnerRegistrar.java create mode 100644 src/main/java/tk/antoine_roux/wiki/annotation/ApiPrefix.java create mode 100644 src/main/java/tk/antoine_roux/wiki/annotation/ApiVersion.java create mode 100644 src/main/java/tk/antoine_roux/wiki/configuration/ApiVersionRequestMappingHandlerMapping.java create mode 100644 src/main/java/tk/antoine_roux/wiki/configuration/WebConfiguration.java diff --git a/doc-gitlab-runner.md b/doc-gitlab-runner.md new file mode 100644 index 0000000..14353c5 --- /dev/null +++ b/doc-gitlab-runner.md @@ -0,0 +1,10 @@ +# gitlab runner + +command used to register new runner + +```shell script +$ docker run -d --name gitlab-runner --restart always -v /var/run/docker.sock:/var/run/docker.sock -v gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:latest + +$ gitlab-runner register -non-interactive --description "manualy registered gitlab runner" --url "http://172.17.0.1 +:8080/" --registration-token "registration_token" --tag-list "docker,manual" +``` diff --git a/pom.xml b/pom.xml index 6ae4dc3..efdbc33 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,10 @@ org.springframework.boot spring-boot-starter-undertow --> + + org.springframework.boot + spring-boot-starter-actuator + diff --git a/src/main/java/tk/antoine_roux/wiki/Constant.java b/src/main/java/tk/antoine_roux/wiki/Constant.java new file mode 100644 index 0000000..815dcb8 --- /dev/null +++ b/src/main/java/tk/antoine_roux/wiki/Constant.java @@ -0,0 +1,11 @@ +package tk.antoine_roux.wiki; + +/** + * Application level constant + */ +public final class Constant { + public static final String API_PREFIX = "/api"; + public static final String API_NAME = "gitlab-runner-gateway"; + public static final String API_VERSION = "4"; + public static final String VERSION_PREFIX = "v"; +} diff --git a/src/main/java/tk/antoine_roux/wiki/ControllerHandlers.java b/src/main/java/tk/antoine_roux/wiki/ControllerHandlers.java new file mode 100644 index 0000000..21f774d --- /dev/null +++ b/src/main/java/tk/antoine_roux/wiki/ControllerHandlers.java @@ -0,0 +1,58 @@ +package tk.antoine_roux.wiki; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import tk.antoine_roux.wiki.annotation.ApiPrefix; +import tk.antoine_roux.wiki.annotation.ApiVersion; + +import java.util.Map; +import java.util.TreeMap; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static tk.antoine_roux.wiki.Constant.*; + +@RestController +@ApiPrefix(API_PREFIX) +public class ControllerHandlers { + + private RunnerRegistrar runnerRegistrar; + + @Autowired + public ControllerHandlers(RunnerRegistrar runnerRegistrar) { + this.runnerRegistrar = runnerRegistrar; + } + + /** + * add register new runners + * + * @param body + * @return + */ + @ResponseBody + @ApiVersion({API_VERSION}) + @PostMapping(value = "/runners", produces = APPLICATION_JSON_VALUE) + public static ResponseEntity addRunner(@RequestBody String body) { + System.out.println(body); + return ResponseEntity.ok(body); + } + + /** + * hello test endpoint + * + * @return + */ + @ResponseBody + @GetMapping("/info") + public static ResponseEntity> info() { + return ResponseEntity.ok( + // sort attribute by key name + new TreeMap<>( + Map.of( + "api-version", VERSION_PREFIX + API_VERSION, + "api-name", API_NAME + ) + ) + ); + } +} diff --git a/src/main/java/tk/antoine_roux/wiki/MainLauncher.java b/src/main/java/tk/antoine_roux/wiki/MainLauncher.java index 31c634d..3a6f6cc 100644 --- a/src/main/java/tk/antoine_roux/wiki/MainLauncher.java +++ b/src/main/java/tk/antoine_roux/wiki/MainLauncher.java @@ -1,9 +1,7 @@ package tk.antoine_roux.wiki; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; import org.springframework.web.servlet.function.RouterFunction; import org.springframework.web.servlet.function.RouterFunctions; import org.springframework.web.servlet.function.ServerResponse; @@ -15,9 +13,6 @@ import org.springframework.web.servlet.function.ServerResponse; @SpringBootApplication(proxyBeanMethods = false) public class MainLauncher { - @Value("${wikiproject.basePath}") - private String basePath; - /** * Entrypoint for application */ @@ -28,11 +23,11 @@ public class MainLauncher { /** * Routing declaration */ - @Bean + // @Bean public RouterFunction routes() { return RouterFunctions.route() - .GET(this.basePath + "/hello", serverRequest -> - ServerResponse.ok().body("Hello world !") - ).build(); + // .POST("/runners", accept(APPLICATION_FORM_URLENCODED), ControllerHandlers::addRunner) + // .GET("/hello", ControllerHandlers::hello) + .build(); } } diff --git a/src/main/java/tk/antoine_roux/wiki/RunnerRegistrar.java b/src/main/java/tk/antoine_roux/wiki/RunnerRegistrar.java new file mode 100644 index 0000000..cb66987 --- /dev/null +++ b/src/main/java/tk/antoine_roux/wiki/RunnerRegistrar.java @@ -0,0 +1,41 @@ +package tk.antoine_roux.wiki; + +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class use to perist registered gitlab runner + */ +@Service +public class RunnerRegistrar { + private final List runners = new ArrayList<>(); + + public List getRunners() { + return this.runners; + } + + public void addRunner(Runner r) { + this.runners.add(r); + } + + /** + * in memory representation of gitlab runner + */ + public static class Runner { + private static final String TAG_SEPARATOR = ","; + + public String id; + public String description; + public String[] tags; + public String registrationToken; + + public Runner(String id, String description, String tags, String registrationToken) { + this.id = id; + this.description = description; + this.tags = tags.split(TAG_SEPARATOR); + this.registrationToken = registrationToken; + } + } +} diff --git a/src/main/java/tk/antoine_roux/wiki/annotation/ApiPrefix.java b/src/main/java/tk/antoine_roux/wiki/annotation/ApiPrefix.java new file mode 100644 index 0000000..2d2b922 --- /dev/null +++ b/src/main/java/tk/antoine_roux/wiki/annotation/ApiPrefix.java @@ -0,0 +1,12 @@ +package tk.antoine_roux.wiki.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiPrefix { + String value() default ""; +} diff --git a/src/main/java/tk/antoine_roux/wiki/annotation/ApiVersion.java b/src/main/java/tk/antoine_roux/wiki/annotation/ApiVersion.java new file mode 100644 index 0000000..11d6c93 --- /dev/null +++ b/src/main/java/tk/antoine_roux/wiki/annotation/ApiVersion.java @@ -0,0 +1,12 @@ +package tk.antoine_roux.wiki.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiVersion { + String[] value() default {""}; +} diff --git a/src/main/java/tk/antoine_roux/wiki/configuration/ApiVersionRequestMappingHandlerMapping.java b/src/main/java/tk/antoine_roux/wiki/configuration/ApiVersionRequestMappingHandlerMapping.java new file mode 100644 index 0000000..6955534 --- /dev/null +++ b/src/main/java/tk/antoine_roux/wiki/configuration/ApiVersionRequestMappingHandlerMapping.java @@ -0,0 +1,91 @@ +package tk.antoine_roux.wiki.configuration; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.servlet.mvc.condition.*; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import tk.antoine_roux.wiki.annotation.ApiPrefix; +import tk.antoine_roux.wiki.annotation.ApiVersion; + +import java.lang.reflect.Method; + +/** + * create custom {@link org.springframework.web.bind.annotation.RequestMapping} Handler to add versioning into uri + */ +public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping { + + private final String versionPrefix; + + /** + * create @{@link org.springframework.web.bind.annotation.RequestMapping} handler classe + * this class add some prefix to uri + * + * @param versionPrefix + */ + public ApiVersionRequestMappingHandlerMapping(String versionPrefix) { + this.versionPrefix = versionPrefix; + } + + @Override + protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) { + RequestMappingInfo info = super.getMappingForMethod(method, handlerType); + if (info == null) { + return null; + } + + ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class); + if (methodAnnotation != null) { + RequestCondition methodCondition = this.getCustomMethodCondition(method); + // Concatenate our ApiVersion with the usual request mapping + info = this.createApiVersionInfo(methodAnnotation, methodCondition).combine(info); + } else { + ApiVersion typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class); + if (typeAnnotation != null) { + RequestCondition typeCondition = this.getCustomTypeCondition(handlerType); + // Concatenate our ApiVersion with the usual request mapping + info = this.createApiVersionInfo(typeAnnotation, typeCondition).combine(info); + } + } + + ApiPrefix annotationApiPrefix = AnnotationUtils.findAnnotation(handlerType, ApiPrefix.class); + if (annotationApiPrefix != null) { + RequestMappingInfo requestMappingInfo = new RequestMappingInfo( + new PatternsRequestCondition( + new String[]{annotationApiPrefix.value()}, + this.getUrlPathHelper(), this.getPathMatcher(), + false + ), + new RequestMethodsRequestCondition(), + new ParamsRequestCondition(), + new HeadersRequestCondition(), + new ConsumesRequestCondition(), + new ProducesRequestCondition(), + null); + info = requestMappingInfo.combine(info); + } + + return info; + } + + private RequestMappingInfo createApiVersionInfo(ApiVersion annotation, RequestCondition customCondition) { + String[] values = annotation.value(); + String[] patterns = new String[values.length]; + for (int i = 0; i < values.length; i++) { + // Build the URL prefix + patterns[i] = this.versionPrefix + values[i]; + } + + return new RequestMappingInfo( + new PatternsRequestCondition( + patterns, this.getUrlPathHelper(), this.getPathMatcher(), + false + // , this.useTrailingSlashMatch(), this.getFileExtensions() + ), + new RequestMethodsRequestCondition(), + new ParamsRequestCondition(), + new HeadersRequestCondition(), + new ConsumesRequestCondition(), + new ProducesRequestCondition(), + customCondition); + } +} diff --git a/src/main/java/tk/antoine_roux/wiki/configuration/WebConfiguration.java b/src/main/java/tk/antoine_roux/wiki/configuration/WebConfiguration.java new file mode 100644 index 0000000..b7907dd --- /dev/null +++ b/src/main/java/tk/antoine_roux/wiki/configuration/WebConfiguration.java @@ -0,0 +1,25 @@ +package tk.antoine_roux.wiki.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.web.accept.ContentNegotiationManager; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.servlet.resource.ResourceUrlProvider; +import tk.antoine_roux.wiki.Constant; + +/** + * spring web configuration + */ +@Configuration +public class WebConfiguration extends WebMvcConfigurationSupport { + + @Bean + @Override + public RequestMappingHandlerMapping requestMappingHandlerMapping(ContentNegotiationManager contentNegotiationManager, + FormattingConversionService conversionService, + ResourceUrlProvider resourceUrlProvider) { + return new ApiVersionRequestMappingHandlerMapping(Constant.VERSION_PREFIX); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c177a09..822417f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,15 @@ spring.main.banner-mode=off -spring.main.lazy-initialization=true +spring.main.lazy-initialization=false spring.output.ansi.enabled=ALWAYS server.port=^application.port^ -wikiproject.basePath=/wikiproject +# wikiproject.basePath=/api + +logging.level.root=INFO + +# spring boot actuator +management.server.port=8080 +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