Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
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;
import org.qubership.colly.dto.PatchEnvironmentDto;
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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -50,6 +55,7 @@ void syncAll() {
Log.info("Task for loading data from git has started");
List<Project> projects = projectRepoLoader.loadProjects();
projects.forEach(projectRepository::persist);
syncInfoRepository.saveLastProjectSyncAt(Instant.now());
Log.info("Projects loaded: " + projects.size());
List<CloudPassport> cloudPassports = cloudPassportLoader.loadCloudPassports(projects);
Log.info("Cloud passports loaded: " + cloudPassports.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.*;
Expand All @@ -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
Expand Down Expand Up @@ -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());
}

}

Original file line number Diff line number Diff line change
@@ -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<String, String, String> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Loading