Merge pull request 'develop' (#49) from develop into main
ogcore-debian-package/pipeline/head This commit looks good Details
testing/ogcore-api/pipeline/head This commit looks good Details
ogcore-debian-package/pipeline/tag This commit looks good Details

Reviewed-on: #49
pull/50/head 0.22.0
Manuel Aranda Rosales 2025-09-03 07:02:09 +02:00
commit aff9419e1c
6 changed files with 260 additions and 63 deletions

View File

@ -1,5 +1,10 @@
# Changelog
## [0.22.0] - 2025-09-03
### Added
- Se han mejorado el control de errores respecto a ogRepository
- Se ha añadido una validacion para impedir el deploy de imagenes que no esten en "success".
---
## [0.21.0] - 2025-08-28
### Added
- Se ha incluido una integracion con el agente, para generar los scripts que se llaman al particionar. Nuevo servicio.

View File

@ -45,6 +45,10 @@ class DeployImageAction extends AbstractController
*/
public function __invoke(DeployImageInput $input, ImageImageRepository $image): JsonResponse
{
if ($image->getStatus() !== ImageStatus::SUCCESS) {
return new BadRequestHttpException('Image is not ready to be deployed');
}
$this->validator->validate($input);
$clientJobs = [];

View File

@ -48,19 +48,48 @@ abstract class AbstractOgRepositoryController extends AbstractController
'timeout' => 30,
]);
$this->logger->info('Sending HTTP request', [
'method' => $method,
'url' => $url,
'params' => $params
]);
try {
$response = $this->httpClient->request($method, $url, $params);
return json_decode($response->getContent(), true);
$content = $response->getContent();
$decodedContent = json_decode($content, true);
$this->logger->info('HTTP response received successfully', [
'method' => $method,
'url' => $url,
'status_code' => $response->getStatusCode(),
'response_size' => strlen($content),
'decoded_content' => $decodedContent
]);
return $decodedContent;
} catch (ClientExceptionInterface | ServerExceptionInterface $e) {
$this->logger->error(sprintf('Client/Server error in request to %s: %s', $url, $e->getMessage()));
$statusCode = $e->getResponse()?->getStatusCode() ?? Response::HTTP_INTERNAL_SERVER_ERROR;
$this->logger->error('Client/server error in HTTP request', [
'method' => $method,
'url' => $url,
'error' => $e->getMessage(),
'status_code' => $statusCode,
'response_content' => $e->getResponse()?->getContent(false)
]);
return [
'code' => Response::HTTP_INTERNAL_SERVER_ERROR,
'code' => $statusCode,
'error' => $e->getMessage(),
'details' => $e->getMessage(),
'details' => $e->getResponse()?->getContent(false) ?? $e->getMessage(),
];
} catch (TransportExceptionInterface $e) {
$this->logger->error(sprintf('Transport error in request to %s: %s', $url, $e->getMessage()));
$this->logger->error('Transport error in HTTP request', [
'method' => $method,
'url' => $url,
'error' => $e->getMessage()
]);
return [
'code' => Response::HTTP_INTERNAL_SERVER_ERROR,

View File

@ -34,10 +34,26 @@ class BackupImageAction extends AbstractOgRepositoryController
$image = $imageImageRepository->getImage();
$repository = $imageImageRepository->getRepository();
$this->validateImageName($image);
$this->logger->info('Create backup image', ['image' => $image->getName()]);
$content = $this->createBackupRequest($input, $imageImageRepository, $repository);
$this->handleBackupResponse($content);
$this->processBackupSuccess($input, $image, $repository, $imageImageRepository, $content);
return new JsonResponse(data: $content, status: Response::HTTP_OK);
}
private function validateImageName(Image $image): void
{
if (!$image->getName()) {
throw new BadRequestHttpException('Name is required');
}
}
private function createBackupRequest(BackupImageInput $input, ImageImageRepository $imageImageRepository, ImageRepository $repository): array
{
$params = [
'json' => [
'ID_img' => $imageImageRepository->getImageFullsum(),
@ -48,16 +64,85 @@ class BackupImageAction extends AbstractOgRepositoryController
]
];
$this->logger->info('Create backup image', ['image' => $image->getName()]);
return $this->createRequest('PUT', 'http://'.$repository->getIp().':8006/ogrepository/v1/repo/images', $params);
}
$repository = $imageImageRepository->getRepository();
$content = $this->createRequest('PUT', 'http://'.$repository->getIp().':8006/ogrepository/v1/repo/images', $params);
if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR ) {
throw new BadRequestHttpException('Error backing up image: ' . $content['error'] . ' - ' . $content['details']);
private function handleBackupResponse(array $content): void
{
if (!isset($content['error'])) {
return;
}
$errorMessage = $this->extractErrorMessage($content);
$errorCode = $content['code'] ?? Response::HTTP_BAD_REQUEST;
$this->throwAppropriateException($errorCode, $errorMessage);
}
private function extractErrorMessage(array $content): string
{
$errorMessage = $content['error'];
$errorDetails = $content['details'] ?? null;
if (!$errorDetails) {
return $errorMessage;
}
$extractedMessage = $this->extractMessageFromDetails($errorDetails);
return $extractedMessage ?: $errorMessage;
}
private function extractMessageFromDetails($errorDetails): ?string
{
if (is_array($errorDetails)) {
return $errorDetails['details'] ?? $errorDetails['message'] ?? $errorDetails['error'] ?? null;
}
if (is_string($errorDetails)) {
$parsed = $this->parseJsonString($errorDetails);
if ($parsed !== null) {
return $parsed['details'] ?? $parsed['message'] ?? $parsed['error'] ?? null;
}
return !empty($errorDetails) ? $errorDetails : null;
}
return null;
}
private function parseJsonString(string $jsonString): ?array
{
$cleanJson = trim($jsonString, " \t\n\r\0\x0B");
$decoded = json_decode($cleanJson, true);
return (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) ? $decoded : null;
}
private function throwAppropriateException(int $errorCode, string $errorMessage): void
{
if ($errorCode === Response::HTTP_INTERNAL_SERVER_ERROR) {
throw new BadRequestHttpException('Error backing up image: ' . $errorMessage);
}
if ($errorCode >= 400 && $errorCode < 500) {
throw new BadRequestHttpException($errorMessage);
}
if ($errorCode >= 500) {
throw new BadRequestHttpException('Error backing up image: ' . $errorMessage);
}
throw new BadRequestHttpException($errorMessage);
}
private function processBackupSuccess(
BackupImageInput $input,
Image $image,
ImageRepository $repository,
ImageImageRepository $imageImageRepository,
array $content
): void {
$inputData = [
'imageName' => $image->getName(),
'repositoryUuid' => $repository->getUuid(),
@ -67,13 +152,16 @@ class BackupImageAction extends AbstractOgRepositoryController
'remote_path' => $input->remotePath
];
$this->createService->__invoke($image->getClient(), CommandTypes::BACKUP_IMAGE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData);
$this->createService->__invoke(
$image->getClient(),
CommandTypes::BACKUP_IMAGE,
TraceStatus::IN_PROGRESS,
$content['job_id'],
$inputData
);
$imageImageRepository->setStatus(ImageStatus::BACKUP);
$this->entityManager->persist($imageImageRepository);
$this->entityManager->flush();
return new JsonResponse(data: $content, status: Response::HTTP_OK);
}
}

View File

@ -19,6 +19,7 @@ use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
#[AsController]
class ImportAction extends AbstractOgRepositoryController
@ -30,20 +31,52 @@ class ImportAction extends AbstractOgRepositoryController
* @throws ClientExceptionInterface
*/
public function __invoke(ImportImageRepositoryInput $input, ImageRepository $repository): JsonResponse
{
$this->validateRepositoryStatus($repository);
$image = $input->name;
$imageEntity = $this->getOrCreateImageEntity($image);
$this->validateImageNotExistsInRepository($imageEntity, $repository);
$this->logger->info('Creating aux files', ['image' => $image]);
$content = $this->createTorrentSumRequest($repository, $image);
$this->handleTorrentSumResponse($content);
$imageImageRepositoryEntity = $this->createImageImageRepositoryEntity($imageEntity, $repository, $image);
$this->entityManager->persist($imageImageRepositoryEntity);
$this->entityManager->flush();
$this->createService->__invoke(
null,
CommandTypes::CREATE_IMAGE_AUX_FILE,
TraceStatus::IN_PROGRESS,
$content['job_id'],
[
'imageName' => $image,
'imageImageRepositoryUuid' => $imageImageRepositoryEntity->getUuid(),
]
);
return new JsonResponse(data: [], status: Response::HTTP_OK);
}
private function validateRepositoryStatus(ImageRepository $repository): void
{
$content = $this->createRequest('GET', 'http://'.$repository->getIp(). ':8006/ogrepository/v1/status');
if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) {
throw new BadRequestHttpException('An error occurred while fetching the status: ' . $content['details']);
}
}
$image = $input->name;
private function getOrCreateImageEntity(string $imageName): Image
{
$imageEntity = $this->entityManager->getRepository(Image::class)->findOneBy(['name' => $imageName]);
$imageEntity = $this->entityManager->getRepository(Image::class)->findOneBy(['name' => $image]);
if (!$imageEntity){
if (!$imageEntity) {
$imageEntity = new Image();
$imageEntity->setName($image);
$imageEntity->setName($imageName);
$imageEntity->setType('monolithic');
$imageEntity->setRemotePc(false);
$imageEntity->setIsGlobal(false);
@ -51,65 +84,97 @@ class ImportAction extends AbstractOgRepositoryController
$this->entityManager->persist($imageEntity);
}
$imageImageRepositoryEntity = $this->entityManager->getRepository(ImageImageRepository::class)->findOneBy(['image' => $imageEntity, 'repository' => $repository]);
return $imageEntity;
}
if ($imageImageRepositoryEntity){
private function validateImageNotExistsInRepository(Image $imageEntity, ImageRepository $repository): void
{
$imageImageRepositoryEntity = $this->entityManager->getRepository(ImageImageRepository::class)
->findOneBy(['image' => $imageEntity, 'repository' => $repository]);
if ($imageImageRepositoryEntity) {
throw new BadRequestHttpException('This image already exists in this repository');
}
}
$imageImageRepositoryEntity = new ImageImageRepository();
$imageImageRepositoryEntity->setName($imageEntity->getName());
$imageImageRepositoryEntity->setStatus(ImageStatus::AUX_FILES_PENDING);
$imageImageRepositoryEntity->setImage($imageEntity);
$imageImageRepositoryEntity->setRepository($repository);
$imageImageRepositoryEntity->setVersion($this->extractVersionFromImageName($image));
$this->entityManager->persist($imageImageRepositoryEntity);
$this->entityManager->flush();
$this->logger->info('Creating aux files', ['image' => $image]);
private function createTorrentSumRequest(ImageRepository $repository, string $image): array
{
$params = [
'json' => [
'image' => $image.'.img'
]
];
$content = $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/images/torrentsum', $params);
if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR ) {
throw new BadRequestHttpException('Error importing image' . ' - ' . $content['error'] . ' - ' . $content['details']);
}
$inputData = [
'imageName' => $image,
'imageImageRepositoryUuid' => $imageImageRepositoryEntity->getUuid(),
];
$this->createService->__invoke(null, CommandTypes::CREATE_IMAGE_AUX_FILE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData);
return new JsonResponse(data: [], status: Response::HTTP_OK);
return $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/images/torrentsum', $params);
}
private function handleTorrentSumResponse(array $content): void
{
if (!isset($content['error'])) {
return;
}
$errorMessage = $this->extractErrorMessage($content);
$errorCode = $content['code'] ?? Response::HTTP_BAD_REQUEST;
$this->throwAppropriateException($errorCode, $errorMessage);
}
private function extractErrorMessage(array $content): string
{
$errorMessage = $content['error'];
$errorDetails = $content['details'] ?? null;
if (!$errorDetails) {
return $errorMessage;
}
if (is_array($errorDetails)) {
return $errorDetails['message'] ?? $errorDetails['error'] ?? $errorMessage;
}
if (is_string($errorDetails) && !empty($errorDetails)) {
return $errorDetails;
}
return $errorMessage;
}
private function throwAppropriateException(int $errorCode, string $errorMessage): void
{
if ($errorCode >= 400 && $errorCode < 500) {
throw new BadRequestHttpException($errorMessage);
}
if ($errorCode >= 500) {
throw new HttpException($errorCode, $errorMessage);
}
throw new BadRequestHttpException($errorMessage);
}
private function createImageImageRepositoryEntity(Image $imageEntity, ImageRepository $repository, string $imageName): ImageImageRepository
{
$imageImageRepositoryEntity = new ImageImageRepository();
$imageImageRepositoryEntity->setName($imageEntity->getName());
$imageImageRepositoryEntity->setStatus(ImageStatus::AUX_FILES_PENDING);
$imageImageRepositoryEntity->setImage($imageEntity);
$imageImageRepositoryEntity->setRepository($repository);
$imageImageRepositoryEntity->setVersion($this->extractVersionFromImageName($imageName));
return $imageImageRepositoryEntity;
}
private function extractVersionFromImageName(string $imageName): int
{
// Buscar patrones como "_v2", "_v3", etc.
if (preg_match('/_v(\d+)$/', $imageName, $matches)) {
return (int) $matches[1];
$patterns = ['/_v(\d+)$/', '/-v(\d+)$/', '/v(\d+)$/'];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $imageName, $matches)) {
return (int) $matches[1];
}
}
// Buscar patrones como "-v2", "-v3", etc.
if (preg_match('/-v(\d+)$/', $imageName, $matches)) {
return (int) $matches[1];
}
// Buscar patrones como "v2", "v3" al final del nombre
if (preg_match('/v(\d+)$/', $imageName, $matches)) {
return (int) $matches[1];
}
// Si no se encuentra ningún patrón de versión, devolver 1 por defecto
return 1;
}
}

View File

@ -26,6 +26,12 @@ class ResponseController extends AbstractOgRepositoryController
public function repositoryWebhook(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
$this->logger->info('Webhook data received', [
'payload' => $data,
'raw_content' => $request->getContent(),
'headers' => $request->headers->all()
]);
if (!isset($data['job_id'])) {
return $this->jsonResponseError('Invalid request', Response::HTTP_BAD_REQUEST);