From 20b9ffdfa1b1bc80a9c0a43234834f3e8fb2b55b Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 3 Sep 2025 06:51:54 +0200 Subject: [PATCH 1/3] refs #2735. Error handler ogRepo --- .../OgRepository/Image/BackupImageAction.php | 110 ++++++++++-- .../OgRepository/Image/ImportAction.php | 159 ++++++++++++------ 2 files changed, 211 insertions(+), 58 deletions(-) diff --git a/src/Controller/OgRepository/Image/BackupImageAction.php b/src/Controller/OgRepository/Image/BackupImageAction.php index aaf3d1f..3a2c2ac 100644 --- a/src/Controller/OgRepository/Image/BackupImageAction.php +++ b/src/Controller/OgRepository/Image/BackupImageAction.php @@ -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); } } \ No newline at end of file diff --git a/src/Controller/OgRepository/Image/ImportAction.php b/src/Controller/OgRepository/Image/ImportAction.php index 31c501c..667c4fa 100644 --- a/src/Controller/OgRepository/Image/ImportAction.php +++ b/src/Controller/OgRepository/Image/ImportAction.php @@ -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; } } \ No newline at end of file From a30ee652b7f759fd3b01fa24b5c2725483650e36 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 3 Sep 2025 06:57:47 +0200 Subject: [PATCH 2/3] refs #2736. Deploy image handler status --- src/Controller/DeployImageAction.php | 4 ++ .../AbstractOgRepositoryController.php | 39 ++++++++++++++++--- .../Webhook/ResponseController.php | 6 +++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/Controller/DeployImageAction.php b/src/Controller/DeployImageAction.php index 4ca8fc1..5e6d798 100644 --- a/src/Controller/DeployImageAction.php +++ b/src/Controller/DeployImageAction.php @@ -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 = []; diff --git a/src/Controller/OgRepository/AbstractOgRepositoryController.php b/src/Controller/OgRepository/AbstractOgRepositoryController.php index 80824e2..148c8fc 100644 --- a/src/Controller/OgRepository/AbstractOgRepositoryController.php +++ b/src/Controller/OgRepository/AbstractOgRepositoryController.php @@ -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, diff --git a/src/Controller/OgRepository/Webhook/ResponseController.php b/src/Controller/OgRepository/Webhook/ResponseController.php index b8451f8..f334d30 100644 --- a/src/Controller/OgRepository/Webhook/ResponseController.php +++ b/src/Controller/OgRepository/Webhook/ResponseController.php @@ -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); From c20b1c0cfcb19c83d05d3005592faba5da5ab85d Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 3 Sep 2025 07:00:53 +0200 Subject: [PATCH 3/3] Updated changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07280d3..1315b60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.