diff --git a/envgene-inventory-service/src/main/java/org/qubership/colly/CollyStorage.java b/envgene-inventory-service/src/main/java/org/qubership/colly/CollyStorage.java index 75d4a46c..77f27718 100644 --- a/envgene-inventory-service/src/main/java/org/qubership/colly/CollyStorage.java +++ b/envgene-inventory-service/src/main/java/org/qubership/colly/CollyStorage.java @@ -11,6 +11,7 @@ import org.qubership.colly.db.ClusterRepository; import org.qubership.colly.db.EnvironmentRepository; import org.qubership.colly.db.ProjectRepository; +import org.qubership.colly.db.SyncInfoRepository; import org.qubership.colly.db.data.Cluster; import org.qubership.colly.db.data.Environment; import org.qubership.colly.db.data.Namespace; @@ -18,6 +19,7 @@ import org.qubership.colly.projectrepo.Project; import org.qubership.colly.projectrepo.ProjectRepoLoader; +import java.time.Instant; import java.util.Comparator; import java.util.List; import java.util.UUID; @@ -28,6 +30,7 @@ public class CollyStorage { private final ClusterRepository clusterRepository; private final EnvironmentRepository environmentRepository; private final ProjectRepository projectRepository; + private final SyncInfoRepository syncInfoRepository; private final CloudPassportLoader cloudPassportLoader; private final UpdateEnvironmentService updateEnvironmentService; private final ProjectRepoLoader projectRepoLoader; @@ -36,10 +39,12 @@ public class CollyStorage { public CollyStorage( ClusterRepository clusterRepository, EnvironmentRepository environmentRepository, ProjectRepository projectRepository, + SyncInfoRepository syncInfoRepository, CloudPassportLoader cloudPassportLoader, UpdateEnvironmentService updateEnvironmentService, ProjectRepoLoader projectRepoLoader) { this.clusterRepository = clusterRepository; this.environmentRepository = environmentRepository; this.projectRepository = projectRepository; + this.syncInfoRepository = syncInfoRepository; this.cloudPassportLoader = cloudPassportLoader; this.updateEnvironmentService = updateEnvironmentService; this.projectRepoLoader = projectRepoLoader; @@ -50,6 +55,7 @@ void syncAll() { Log.info("Task for loading data from git has started"); List projects = projectRepoLoader.loadProjects(); projects.forEach(projectRepository::persist); + syncInfoRepository.saveLastProjectSyncAt(Instant.now()); Log.info("Projects loaded: " + projects.size()); List cloudPassports = cloudPassportLoader.loadCloudPassports(projects); Log.info("Cloud passports loaded: " + cloudPassports.size()); diff --git a/envgene-inventory-service/src/main/java/org/qubership/colly/InventoryServiceRest.java b/envgene-inventory-service/src/main/java/org/qubership/colly/InventoryServiceRest.java index 3d7f8913..19fa28fa 100644 --- a/envgene-inventory-service/src/main/java/org/qubership/colly/InventoryServiceRest.java +++ b/envgene-inventory-service/src/main/java/org/qubership/colly/InventoryServiceRest.java @@ -7,6 +7,7 @@ import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.media.Content; import org.eclipse.microprofile.openapi.annotations.media.ExampleObject; @@ -16,6 +17,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement; +import org.qubership.colly.db.SyncInfoRepository; import org.qubership.colly.db.data.Cluster; import org.qubership.colly.db.data.Environment; import org.qubership.colly.dto.*; @@ -32,14 +34,21 @@ public class InventoryServiceRest { private final CollyStorage collyStorage; private final SecurityIdentity securityIdentity; private final DtoMapper dtoMapper; + private final SyncInfoRepository syncInfoRepository; + private final String syncCronSchedule; + @Inject public InventoryServiceRest(CollyStorage collyStorage, SecurityIdentity securityIdentity, - DtoMapper dtoMapper) { + DtoMapper dtoMapper, + SyncInfoRepository syncInfoRepository, + @ConfigProperty(name = "colly.eis.cron.schedule") String syncCronSchedule) { this.collyStorage = collyStorage; this.securityIdentity = securityIdentity; this.dtoMapper = dtoMapper; + this.syncInfoRepository = syncInfoRepository; + this.syncCronSchedule = syncCronSchedule; } @GET @@ -736,5 +745,55 @@ public Response getAuthStatus() { return Response.ok(userInfo).build(); } + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/metadata") + @Operation( + summary = "Get application metadata", + description = "Retrieves application metadata including synchronization schedule. Requires authentication." + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Successfully retrieved application metadata", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = ApplicationMetadataDto.class), + examples = @ExampleObject( + name = "metadata-response", + summary = "Example metadata response", + value = """ + { + syncSchedule: "0 * * * * ?" + } + """ + ) + ) + ), + @APIResponse( + responseCode = "401", + description = "Unauthorized - authentication required", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + examples = @ExampleObject( + value = "{\"error\": \"Authentication required\"}" + ) + ) + ), + @APIResponse( + responseCode = "500", + description = "Internal server error", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + examples = @ExampleObject( + value = "{\"error\": \"Internal server error occurred\"}" + ) + ) + ) + }) + public ApplicationMetadataDto getMetadata() { + return new ApplicationMetadataDto(syncCronSchedule, syncInfoRepository.getLastProjectSyncAt()); + } + } diff --git a/envgene-inventory-service/src/main/java/org/qubership/colly/db/SyncInfoRepository.java b/envgene-inventory-service/src/main/java/org/qubership/colly/db/SyncInfoRepository.java new file mode 100644 index 00000000..0ae6e8f6 --- /dev/null +++ b/envgene-inventory-service/src/main/java/org/qubership/colly/db/SyncInfoRepository.java @@ -0,0 +1,33 @@ +package org.qubership.colly.db; + +import io.quarkus.redis.datasource.RedisDataSource; +import io.quarkus.redis.datasource.hash.HashCommands; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import java.time.Instant; + +@ApplicationScoped +public class SyncInfoRepository { + + private static final String SYNC_INFO_KEY = "inventory:sync-info"; + + @Inject + RedisDataSource redisDataSource; + + private HashCommands hashCommands() { + return redisDataSource.hash(String.class, String.class, String.class); + } + + public void saveLastProjectSyncAt(Instant instant) { + hashCommands().hset(SYNC_INFO_KEY, "lastProjectSyncAt", instant.toString()); + } + + public Instant getLastProjectSyncAt() { + String value = hashCommands().hget(SYNC_INFO_KEY, "lastProjectSyncAt"); + if (value == null) { + return null; + } + return Instant.parse(value); + } +} diff --git a/envgene-inventory-service/src/main/java/org/qubership/colly/dto/ApplicationMetadataDto.java b/envgene-inventory-service/src/main/java/org/qubership/colly/dto/ApplicationMetadataDto.java new file mode 100644 index 00000000..0d905513 --- /dev/null +++ b/envgene-inventory-service/src/main/java/org/qubership/colly/dto/ApplicationMetadataDto.java @@ -0,0 +1,21 @@ +package org.qubership.colly.dto; + +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.time.Instant; + +@Schema(description = "Application metadata including sync info") +public record ApplicationMetadataDto( + @Schema( + description = "Cron expression defining the schedule for synchronization with Projects Git", + examples = "0 * * * * ?", + required = true + ) + String syncSchedule, + @Schema( + description = "Timestamp of the last project repository synchronization", + examples = "2025-01-15T10:30:00Z" + ) + Instant lastProjectSyncAt +) { +} diff --git a/envgene-inventory-service/src/test/java/org/qubership/InventoryServiceRestTest.java b/envgene-inventory-service/src/test/java/org/qubership/InventoryServiceRestTest.java index 47660cbd..d4a07e8a 100644 --- a/envgene-inventory-service/src/test/java/org/qubership/InventoryServiceRestTest.java +++ b/envgene-inventory-service/src/test/java/org/qubership/InventoryServiceRestTest.java @@ -621,4 +621,27 @@ void update_environment_with_empty_fields() { .body("find { it.name == 'env-metadata-test' }.owners", emptyIterable()) .body("find { it.name == 'env-metadata-test' }.expirationDate", nullValue()); } + + @Test + void get_metadata_without_auth() { + given() + .when().get("/colly/v2/inventory-service/metadata") + .then() + .statusCode(401); + } + + @Test + @TestSecurity(user = "test") + void get_metadata() { + given() + .when().post("/colly/v2/inventory-service/manual-sync") + .then() + .statusCode(204); + given() + .when().get("/colly/v2/inventory-service/metadata") + .then() + .statusCode(200) + .body("syncSchedule", equalTo("0 0 0 1 1 ? 2020")) + .body("lastProjectSyncAt", notNullValue()); + } }