From 217f8348afe5295ef8b86f97927c527147f14678 Mon Sep 17 00:00:00 2001 From: Gaurav Mishra Date: Thu, 14 Nov 2024 18:42:47 +0530 Subject: [PATCH] fix(openapi)!: add health endpoint to openapi Rearrange the application.yml in test to look more like source. BREAKING CHANGE: Move the health endpoint from `/resource/health` to `/resource/api/health` for OpenAPI documentations to be easy to use. Signed-off-by: Gaurav Mishra --- .../resourceserver/Sw360ResourceServer.java | 44 +++++++++++++++++-- .../project/ProjectController.java | 2 +- .../security/ResourceServerConfiguration.java | 8 ++-- .../src/main/resources/application.yml | 10 ++++- .../SW360RestHealthIndicatorTest.java | 2 +- ...Sw360AuthorizationServerConfiguration.java | 4 +- .../restdocs/HealthSpecTest.java | 4 +- .../src/test/resources/application.yml | 41 ++++++++++------- 8 files changed, 86 insertions(+), 29 deletions(-) diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java index c4634e0c81..051443da05 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java @@ -10,10 +10,17 @@ package org.eclipse.sw360.rest.resourceserver; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import org.eclipse.sw360.datahandler.common.CommonUtils; @@ -25,6 +32,7 @@ import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationFilter; import org.springdoc.core.utils.SpringDocUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.health.Health; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -45,7 +53,7 @@ @Import(Sw360CORSFilter.class) public class Sw360ResourceServer extends SpringBootServletInitializer { - private static final String REST_BASE_PATH = "/api"; + public static final String REST_BASE_PATH = "/api"; @Value("${spring.data.rest.default-page-size:10}") private int defaultPageSize; @@ -168,6 +176,36 @@ public OpenAPI customOpenAPI() { .info(new Info().title("SW360 API").license(new License().name("EPL-2.0") .url("https://github.com/eclipse-sw360/sw360/blob/main/LICENSE")) .version(restVersionString)) - .servers(List.of(server)); + .servers(List.of(server)) + .path("/health", new PathItem().get( + new Operation().tags(Collections.singletonList("Health")) + .summary("Health endpoint").operationId("health") + .responses(new ApiResponses().addApiResponse("200", + new ApiResponse().description("OK") + .content(new Content() + .addMediaType("application/json", new MediaType() + .example(""" + { + "status": "UP", + "components": { + "SW360Rest": { + "status": "UP", + "details": { + "Rest State": { + "isDbReachable": true, + "isThriftReachable": true + } + } + }, + "ping": { + "status": "UP" + } + } + } + """) + .schema(new Schema()) + )) + )) + )); } -} \ No newline at end of file +} diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java index 98083a1423..58a96a36d2 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java @@ -3264,7 +3264,7 @@ public ResponseEntity createDuplicateProjectWithDependencyNetwork( if (reqBodyMap.get("dependencyNetwork") != null) { try { addOrPatchDependencyNetworkToProject(duplicatedProject, reqBodyMap, ProjectOperation.CREATE); - } catch (JsonProcessingException | NoSuchElementException | InvalidPropertiesFormatException e) { + } catch (JsonProcessingException | NoSuchElementException e) { log.error(e.getMessage()); return ResponseEntity.badRequest().body(e.getMessage()); } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java index 7263178062..b9ac930ad0 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java @@ -60,7 +60,9 @@ public class ResourceServerConfiguration { @Bean public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring().requestMatchers("/", "/*/*.html", "/*/*.css", "/*/*.js", "/*.js", "/*.json", "/*/*.json", "/*/*.png", "/*/*.gif", "/*/*.ico", "/*/*.woff/*", "/*/*.ttf", "/*/*.html", "/*/*/*.html", "/*/*.yaml", "/v3/api-docs/**"); + return (web) -> web.ignoring().requestMatchers("/", "/*/*.html", "/*/*.css", "/*/*.js", "/*.js", "/*.json", + "/*/*.json", "/*/*.png", "/*/*.gif", "/*/*.ico", "/*/*.woff/*", "/*/*.ttf", "/*/*.html", "/*/*/*.html", + "/*/*.yaml", "/v3/api-docs/**", "/api/health", "/api/info"); } @Bean @@ -80,8 +82,8 @@ public SecurityFilterChain securityFilterChainRS1(HttpSecurity http) throws Exce public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { SimpleAuthenticationEntryPoint saep = new SimpleAuthenticationEntryPoint(); return http.addFilterBefore(filter, BasicAuthenticationFilter.class).authorizeHttpRequests(auth -> { - auth.requestMatchers(HttpMethod.GET, "/health").permitAll(); - auth.requestMatchers(HttpMethod.GET, "/info").hasAuthority("WRITE"); + auth.requestMatchers(HttpMethod.GET, "/api/health").permitAll(); + auth.requestMatchers(HttpMethod.GET, "/api/info").hasAuthority("WRITE"); auth.requestMatchers(HttpMethod.GET, "/api").permitAll(); auth.requestMatchers(HttpMethod.GET, "/api/reports/download").permitAll(); auth.requestMatchers(HttpMethod.GET, "/api/**").hasAuthority("READ"); diff --git a/rest/resource-server/src/main/resources/application.yml b/rest/resource-server/src/main/resources/application.yml index 0b3ed316b3..02d3c90f62 100644 --- a/rest/resource-server/src/main/resources/application.yml +++ b/rest/resource-server/src/main/resources/application.yml @@ -20,6 +20,9 @@ management: base-path: / exposure: include: health,info + path-mapping: + health: /api/health + info: /api/info endpoint: health: show-details: always @@ -28,6 +31,11 @@ management: enabled: true security: enabled: true + health: + diskspace: + enabled: true # Disable to hide sensitive system information + ping: + enabled: true spring: application: @@ -83,4 +91,4 @@ springdoc: enabled: true default-consumes-media-type: application/json default-produces-media-type: application/hal+json - paths-to-exclude: /api/** \ No newline at end of file + paths-to-exclude: /api/** diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/SW360RestHealthIndicatorTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/SW360RestHealthIndicatorTest.java index 1d755df974..ed2db2ebb5 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/SW360RestHealthIndicatorTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/SW360RestHealthIndicatorTest.java @@ -71,7 +71,7 @@ public class SW360RestHealthIndicatorTest { */ private ResponseEntity getMapResponseEntityForHealthEndpointRequest(String endpoint) { return this.testRestTemplate.getForEntity( - "http://localhost:" + this.port + endpoint, Map.class); + "http://localhost:" + this.port + Sw360ResourceServer.REST_BASE_PATH + endpoint, Map.class); } @Test diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/configuration/security/Sw360AuthorizationServerConfiguration.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/configuration/security/Sw360AuthorizationServerConfiguration.java index dc9a244ae0..21b51000f7 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/configuration/security/Sw360AuthorizationServerConfiguration.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/configuration/security/Sw360AuthorizationServerConfiguration.java @@ -40,8 +40,8 @@ public SecurityFilterChain appSecurtiy(HttpSecurity httpSecurity) throws Excepti SimpleAuthenticationEntryPoint saep = new SimpleAuthenticationEntryPoint(); httpSecurity.authorizeHttpRequests( authz -> authz - .requestMatchers(HttpMethod.GET, "/health").permitAll() - .requestMatchers(HttpMethod.GET, "/info").permitAll() + .requestMatchers(HttpMethod.GET, "/api/health").permitAll() + .requestMatchers(HttpMethod.GET, "/api/info").permitAll() .anyRequest().authenticated() ).httpBasic(Customizer.withDefaults()).formLogin(Customizer.withDefaults()) .exceptionHandling(x -> x.authenticationEntryPoint(saep)); diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/HealthSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/HealthSpecTest.java index 670382cd66..87a4b538d7 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/HealthSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/HealthSpecTest.java @@ -43,7 +43,7 @@ public void should_document_get_health() throws Exception { given(this.restHealthIndicatorMock.health()).willReturn(spring_health); - mockMvc.perform(get("/health") + mockMvc.perform(get("/api/health") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(this.documentationHandler.document( @@ -72,7 +72,7 @@ public void should_document_get_health_unhealthy() throws Exception { .build(); given(this.restHealthIndicatorMock.health()).willReturn(spring_health_down); - mockMvc.perform(get("/health") + mockMvc.perform(get("/api/health") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()) .andDo(this.documentationHandler.document( diff --git a/rest/resource-server/src/test/resources/application.yml b/rest/resource-server/src/test/resources/application.yml index c703ceea7d..3db9ecabe9 100644 --- a/rest/resource-server/src/test/resources/application.yml +++ b/rest/resource-server/src/test/resources/application.yml @@ -1,21 +1,9 @@ #SPDX-FileCopyrightText: © 2024 Siemens AG #SPDX-License-Identifier: EPL-2.0 -spring: - profiles: - active: SECURITY_MOCK - security: - oauth2: - resourceserver: - jwt: - issuer-uri: http://localhost:8080/authorization/oauth2/jwks - jwk-set-uri: http://localhost:8080/authorization/oauth2/jwks - application: - name: resource +server: servlet: - multipart: - max-file-size: 500MB - max-request-size: 600MB + context-path: / management: endpoints: @@ -23,6 +11,9 @@ management: base-path: / exposure: include: health,info + path-mapping: + health: /api/health + info: /api/info endpoint: health: show-details: always @@ -31,9 +22,27 @@ management: enabled: true security: enabled: true -server: + health: + diskspace: + enabled: true # Disable to hide sensitive system information + ping: + enabled: true + +spring: + profiles: + active: SECURITY_MOCK + application: + name: resource servlet: - context-path: / + multipart: + max-file-size: 500MB + max-request-size: 600MB + security: + oauth2: + resourceserver: + jwt: + issuer-uri: http://localhost:8080/authorization/oauth2/jwks + jwk-set-uri: http://localhost:8080/authorization/oauth2/jwks #logging: # level: