diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 4f7550e..ba28dd3 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -6,6 +6,12 @@ Garden Linux Authors; == Lorem Ipsum +=== Check running app + +include::{snippets}/readiness/curl-request.adoc[] + +include::{snippets}/readiness/http-response.adoc[] + === Get a CVE by id Request diff --git a/src/main/java/io/gardenlinux/glvd/AppController.java b/src/main/java/io/gardenlinux/glvd/AppController.java new file mode 100644 index 0000000..df7d378 --- /dev/null +++ b/src/main/java/io/gardenlinux/glvd/AppController.java @@ -0,0 +1,24 @@ +package io.gardenlinux.glvd; + +import io.gardenlinux.glvd.dto.Readiness; +import io.gardenlinux.glvd.exceptions.DbNotConnectedException; +import jakarta.annotation.Nonnull; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class AppController { + + @Nonnull + private final GlvdService glvdService; + + public AppController(@Nonnull GlvdService glvdService) { + this.glvdService = glvdService; + } + + @GetMapping("/readiness") + public Readiness readiness() throws DbNotConnectedException { + return glvdService.getReadiness(); + } + +} diff --git a/src/main/java/io/gardenlinux/glvd/GlvdController.java b/src/main/java/io/gardenlinux/glvd/GlvdController.java index 25056f6..de4f66f 100644 --- a/src/main/java/io/gardenlinux/glvd/GlvdController.java +++ b/src/main/java/io/gardenlinux/glvd/GlvdController.java @@ -29,7 +29,8 @@ ResponseEntity getCveId(@PathVariable("cveId") final String cveId) throws N } @GetMapping("/{vendor}/{product}/{codename}") - ResponseEntity> getCveDistro(@PathVariable final String vendor, @PathVariable final String product, @PathVariable final String codename) { + ResponseEntity> getCveDistro(@PathVariable final String vendor, @PathVariable final String product, + @PathVariable final String codename) { return ResponseEntity.ok().body(glvdService.getCveForDistribution(vendor, product, codename)); } diff --git a/src/main/java/io/gardenlinux/glvd/GlvdService.java b/src/main/java/io/gardenlinux/glvd/GlvdService.java index 3485627..857cc4a 100644 --- a/src/main/java/io/gardenlinux/glvd/GlvdService.java +++ b/src/main/java/io/gardenlinux/glvd/GlvdService.java @@ -1,7 +1,10 @@ package io.gardenlinux.glvd; import io.gardenlinux.glvd.db.CveRepository; +import io.gardenlinux.glvd.db.HealthCheckRepository; import io.gardenlinux.glvd.dto.Cve; +import io.gardenlinux.glvd.dto.Readiness; +import io.gardenlinux.glvd.exceptions.DbNotConnectedException; import io.gardenlinux.glvd.exceptions.NotFoundException; import jakarta.annotation.Nonnull; import org.springframework.stereotype.Service; @@ -14,8 +17,21 @@ public class GlvdService { @Nonnull private final CveRepository cveRepository; - public GlvdService(@Nonnull CveRepository cveRepository) { + @Nonnull + private final HealthCheckRepository healthCheckRepository; + + public GlvdService(@Nonnull CveRepository cveRepository, @Nonnull HealthCheckRepository healthCheckRepository) { this.cveRepository = cveRepository; + this.healthCheckRepository = healthCheckRepository; + } + + public Readiness getReadiness() throws DbNotConnectedException { + try { + var connection = healthCheckRepository.checkDbConnection(); + return new Readiness(connection); + } catch (Exception e) { + throw new DbNotConnectedException(e); + } } public Cve getCve(String cveId) throws NotFoundException { @@ -28,4 +44,5 @@ public Cve getCve(String cveId) throws NotFoundException { public List getCveForDistribution(String vendor, String product, String codename) { return cveRepository.cvesForDistribution(vendor, product, codename); } + } diff --git a/src/main/java/io/gardenlinux/glvd/db/CveEntity.java b/src/main/java/io/gardenlinux/glvd/db/CveEntity.java index 7d4d63c..df1f17f 100644 --- a/src/main/java/io/gardenlinux/glvd/db/CveEntity.java +++ b/src/main/java/io/gardenlinux/glvd/db/CveEntity.java @@ -9,6 +9,7 @@ @Entity(name = "all_cve") public class CveEntity { + @Id @Column(name = "cve_id", nullable = false) private String id; @@ -46,11 +47,14 @@ public String getData() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; CveEntity cveEntity = (CveEntity) o; - return Objects.equals(id, cveEntity.id) && lastModified.equals(cveEntity.lastModified) && data.equals(cveEntity.data); + return Objects.equals(id, cveEntity.id) && lastModified.equals(cveEntity.lastModified) + && data.equals(cveEntity.data); } @Override @@ -60,4 +64,5 @@ public int hashCode() { result = 31 * result + data.hashCode(); return result; } + } diff --git a/src/main/java/io/gardenlinux/glvd/db/CveRepository.java b/src/main/java/io/gardenlinux/glvd/db/CveRepository.java index 2967510..708701d 100644 --- a/src/main/java/io/gardenlinux/glvd/db/CveRepository.java +++ b/src/main/java/io/gardenlinux/glvd/db/CveRepository.java @@ -22,4 +22,5 @@ INNER JOIN dist_cpe ON (deb_cve.dist_id = dist_cpe.id) all_cve.cve_id """, nativeQuery = true) List cvesForDistribution(String vendor, String product, String codename); + } diff --git a/src/main/java/io/gardenlinux/glvd/db/HealthCheckEntity.java b/src/main/java/io/gardenlinux/glvd/db/HealthCheckEntity.java new file mode 100644 index 0000000..5583cf2 --- /dev/null +++ b/src/main/java/io/gardenlinux/glvd/db/HealthCheckEntity.java @@ -0,0 +1,12 @@ +package io.gardenlinux.glvd.db; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class HealthCheckEntity { + + @Id + private String id; + +} diff --git a/src/main/java/io/gardenlinux/glvd/db/HealthCheckRepository.java b/src/main/java/io/gardenlinux/glvd/db/HealthCheckRepository.java new file mode 100644 index 0000000..92516fd --- /dev/null +++ b/src/main/java/io/gardenlinux/glvd/db/HealthCheckRepository.java @@ -0,0 +1,11 @@ +package io.gardenlinux.glvd.db; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface HealthCheckRepository extends JpaRepository { + + @Query(value = "SELECT TRUE", nativeQuery = true) + String checkDbConnection(); + +} diff --git a/src/main/java/io/gardenlinux/glvd/dto/Cve.java b/src/main/java/io/gardenlinux/glvd/dto/Cve.java index b6d95ac..93b66e8 100644 --- a/src/main/java/io/gardenlinux/glvd/dto/Cve.java +++ b/src/main/java/io/gardenlinux/glvd/dto/Cve.java @@ -5,9 +5,12 @@ import java.util.Objects; public class Cve { + private String id; + @Nonnull private String lastModified; + @Nonnull private String data; @@ -36,8 +39,10 @@ public String getData() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Cve Cve = (Cve) o; return Objects.equals(id, Cve.id) && lastModified.equals(Cve.lastModified) && data.equals(Cve.data); @@ -50,4 +55,5 @@ public int hashCode() { result = 31 * result + data.hashCode(); return result; } + } diff --git a/src/main/java/io/gardenlinux/glvd/dto/Readiness.java b/src/main/java/io/gardenlinux/glvd/dto/Readiness.java new file mode 100644 index 0000000..671a24a --- /dev/null +++ b/src/main/java/io/gardenlinux/glvd/dto/Readiness.java @@ -0,0 +1,33 @@ +package io.gardenlinux.glvd.dto; + +import java.util.Objects; + +public class Readiness { + + private final String dbCheck; + + public Readiness(String dbCheck) { + this.dbCheck = dbCheck; + } + + public String getDbCheck() { + return dbCheck; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Readiness readiness = (Readiness) o; + return Objects.equals(dbCheck, readiness.dbCheck); + } + + @Override + public int hashCode() { + return Objects.hashCode(dbCheck); + } + +} diff --git a/src/main/java/io/gardenlinux/glvd/exceptions/DbNotConnectedException.java b/src/main/java/io/gardenlinux/glvd/exceptions/DbNotConnectedException.java new file mode 100644 index 0000000..261815e --- /dev/null +++ b/src/main/java/io/gardenlinux/glvd/exceptions/DbNotConnectedException.java @@ -0,0 +1,10 @@ +package io.gardenlinux.glvd.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR, reason = "Could not connect to Database. Is it running and configured correctly?") +public class DbNotConnectedException extends Throwable { + public DbNotConnectedException(Exception e) { + } +} diff --git a/src/main/java/io/gardenlinux/glvd/exceptions/NotFoundException.java b/src/main/java/io/gardenlinux/glvd/exceptions/NotFoundException.java index 8eb2c80..413cb54 100644 --- a/src/main/java/io/gardenlinux/glvd/exceptions/NotFoundException.java +++ b/src/main/java/io/gardenlinux/glvd/exceptions/NotFoundException.java @@ -4,5 +4,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not Found") -public class NotFoundException extends Exception{ +public class NotFoundException extends Exception { + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index de878ed..15ee5c3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,3 +3,4 @@ spring.datasource.url=jdbc:postgresql://localhost:5432/glvd spring.datasource.username=glvd spring.datasource.password=glvd spring.sql.init.mode=never +spring.jpa.properties.javax.persistence.query.timeout=5000 \ No newline at end of file diff --git a/src/test/java/io/gardenlinux/glvd/GlvdApplicationTests.java b/src/test/java/io/gardenlinux/glvd/GlvdApplicationTests.java index e4f4746..321cfdc 100644 --- a/src/test/java/io/gardenlinux/glvd/GlvdApplicationTests.java +++ b/src/test/java/io/gardenlinux/glvd/GlvdApplicationTests.java @@ -11,14 +11,14 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Testcontainers class GlvdApplicationTests { + static DockerImageName glvdPostgresImage = DockerImageName.parse("ghcr.io/gardenlinux/glvd-postgres:edgesampledata") .asCompatibleSubstituteFor("postgres"); @Container @ServiceConnection - static PostgreSQLContainer postgres = new PostgreSQLContainer<>( - glvdPostgresImage - ).withDatabaseName("glvd").withUsername("glvd").withPassword("glvd"); + static PostgreSQLContainer postgres = new PostgreSQLContainer<>(glvdPostgresImage).withDatabaseName("glvd") + .withUsername("glvd").withPassword("glvd"); @Test void contextLoads() { diff --git a/src/test/java/io/gardenlinux/glvd/GlvdControllerTest.java b/src/test/java/io/gardenlinux/glvd/GlvdControllerTest.java index 15ef0ef..8473f48 100644 --- a/src/test/java/io/gardenlinux/glvd/GlvdControllerTest.java +++ b/src/test/java/io/gardenlinux/glvd/GlvdControllerTest.java @@ -26,7 +26,6 @@ import org.testcontainers.utility.DockerImageName; import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsString; import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; @@ -43,25 +42,17 @@ class GlvdControllerTest { @Container @ServiceConnection - static PostgreSQLContainer postgres = new PostgreSQLContainer<>( - glvdPostgresImage - ).withDatabaseName("glvd").withUsername("glvd").withPassword("glvd"); + static PostgreSQLContainer postgres = new PostgreSQLContainer<>(glvdPostgresImage).withDatabaseName("glvd") + .withUsername("glvd").withPassword("glvd"); @Autowired CveRepository cveRepository; + @LocalServerPort private Integer port; private RequestSpecification spec; - @BeforeEach - void setUp(RestDocumentationContextProvider restDocumentation) { - this.spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(restDocumentation)).build(); - - RestAssured.baseURI = "http://localhost:" + port; - } - @BeforeAll static void beforeAll() { postgres.start(); @@ -79,47 +70,45 @@ static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.password", postgres::getPassword); } + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { + this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(restDocumentation)).build(); + + RestAssured.baseURI = "http://localhost:" + port; + } + @Test public void shouldGetCveById() { - given(this.spec) - .accept("application/json") + given(this.spec).accept("application/json") .filter(document("getCve", - preprocessRequest(modifyUris() - .scheme("https") - .host("glvd.gardenlinux.io") - .removePort()))) - .when() - .port(this.port) - .get("/v1/cves/CVE-2024-1549") - .then() - .statusCode(HttpStatus.SC_OK) - .body("id", containsString("CVE-2024-1549")); + preprocessRequest(modifyUris().scheme("https").host("glvd.gardenlinux.io").removePort()))) + .when().port(this.port).get("/v1/cves/CVE-2024-1549") + .then().statusCode(HttpStatus.SC_OK).body("id", containsString("CVE-2024-1549")); } @Test void tryGetNonExistingCveById() { - given() - .contentType(ContentType.JSON) - .when() - .get("/v1/cves/CVE-1989-1234") - .then() - .statusCode(HttpStatus.SC_NOT_FOUND); + given().contentType(ContentType.JSON) + .when().get("/v1/cves/CVE-1989-1234") + .then().statusCode(HttpStatus.SC_NOT_FOUND); } @Test public void shouldReturnCvesForBookworm() { - given(this.spec) - .accept("application/json") + given(this.spec).accept("application/json") .filter(document("getCveForDistro", - preprocessRequest(modifyUris() - .scheme("https") - .host("glvd.gardenlinux.io") - .removePort()))) - .when() - .port(this.port) - .get("/v1/cves/debian/debian_linux/bookworm") - .then() - .statusCode(HttpStatus.SC_OK); + preprocessRequest(modifyUris().scheme("https").host("glvd.gardenlinux.io").removePort()))) + .when().port(this.port).get("/v1/cves/debian/debian_linux/bookworm") + .then().statusCode(HttpStatus.SC_OK); + } + + @Test + public void shouldBeReady() { + given(this.spec) + .filter(document("readiness", + preprocessRequest(modifyUris().scheme("https").host("glvd.gardenlinux.io").removePort()))) + .when().port(this.port).get("/readiness") + .then().statusCode(HttpStatus.SC_OK).body("dbCheck", containsString("true")); } } \ No newline at end of file