diff --git a/config/api_platform/ImageRepository.yaml b/config/api_platform/ImageRepository.yaml index d1d04d7..d7b6e79 100644 --- a/config/api_platform/ImageRepository.yaml +++ b/config/api_platform/ImageRepository.yaml @@ -34,7 +34,7 @@ resources: class: ApiPlatform\Metadata\Post method: POST input: App\Dto\Input\WoLInput - uriTemplate: /image-repositories/{uuid}/wol + uriTemplate: /image-repositories/wol controller: App\Controller\OgRepository\WoLAction get_collection_images_ogrepository: diff --git a/src/Controller/OgRepository/AbstractOgRepositoryController.php b/src/Controller/OgRepository/AbstractOgRepositoryController.php index 0d1f141..656df18 100644 --- a/src/Controller/OgRepository/AbstractOgRepositoryController.php +++ b/src/Controller/OgRepository/AbstractOgRepositoryController.php @@ -6,6 +6,7 @@ namespace App\Controller\OgRepository; use App\Service\Trace\CreateService; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -24,6 +25,7 @@ abstract class AbstractOgRepositoryController extends AbstractController protected readonly EntityManagerInterface $entityManager, protected readonly HttpClientInterface $httpClient, protected readonly CreateService $createService, + protected readonly LoggerInterface $logger, ) { } diff --git a/src/Controller/OgRepository/Image/ExportAction.php b/src/Controller/OgRepository/Image/ExportAction.php index 705c23e..7818361 100644 --- a/src/Controller/OgRepository/Image/ExportAction.php +++ b/src/Controller/OgRepository/Image/ExportAction.php @@ -6,6 +6,9 @@ use App\Controller\OgRepository\AbstractOgRepositoryController; use App\Dto\Input\ExportImportImageRepositoryInput; use App\Entity\Image; use App\Entity\ImageRepository; +use App\Model\CommandTypes; +use App\Model\ImageStatus; +use App\Model\TraceStatus; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; @@ -44,9 +47,18 @@ class ExportAction extends AbstractOgRepositoryController ] ]; + $this->logger->info('Exporting image', ['image' => $image->getName(), 'repository' => $repository->getIp()]); + $content = $this->createRequest('PUT', 'http://'.$image->getRepository()->getIp().':8006/ogrepository/v1/repo/images', $params); - $image->setRepository($repository); + $inputData = [ + 'imageName' => $image->getName(), + 'imageUuid' => $image->getUuid(), + ]; + + $this->createService->__invoke($image->getClient(), CommandTypes::EXPORT_IMAGE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData); + + $image->setStatus(ImageStatus::TRANSFERING); $this->entityManager->persist($image); $this->entityManager->flush(); } diff --git a/src/Controller/OgRepository/Image/ImportAction.php b/src/Controller/OgRepository/Image/ImportAction.php index 7d0a7fa..bd37170 100644 --- a/src/Controller/OgRepository/Image/ImportAction.php +++ b/src/Controller/OgRepository/Image/ImportAction.php @@ -6,6 +6,9 @@ use App\Controller\OgRepository\AbstractOgRepositoryController; use App\Dto\Input\ExportImportImageRepositoryInput; use App\Entity\Image; use App\Entity\ImageRepository; +use App\Model\CommandTypes; +use App\Model\ImageStatus; +use App\Model\TraceStatus; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; @@ -46,7 +49,14 @@ class ImportAction extends AbstractOgRepositoryController $content = $this->createRequest('POST', 'http://'.$image->getRepository()->getIp().':8006/ogrepository/v1/repo/images', $params); - $image->setRepository($repository); + $inputData = [ + 'imageName' => $image->getName(), + 'imageUuid' => $image->getUuid(), + ]; + + $this->createService->__invoke($image->getClient(), CommandTypes::IMPORT_IMAGE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData); + + $image->setStatus(ImageStatus::TRANSFERING); $this->entityManager->persist($image); $this->entityManager->flush(); } diff --git a/src/Controller/OgRepository/Webhook/ResponseController.php b/src/Controller/OgRepository/Webhook/ResponseController.php index 7a7d0d2..1838a08 100644 --- a/src/Controller/OgRepository/Webhook/ResponseController.php +++ b/src/Controller/OgRepository/Webhook/ResponseController.php @@ -7,6 +7,7 @@ use App\Entity\Trace; use App\Model\ImageStatus; use App\Model\TraceStatus; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -18,7 +19,8 @@ use Symfony\Component\Routing\Annotation\Route; class ResponseController extends AbstractController { public function __construct( - protected readonly EntityManagerInterface $entityManager + protected readonly EntityManagerInterface $entityManager, + protected readonly LoggerInterface $logger, ) { } @@ -28,6 +30,27 @@ class ResponseController extends AbstractController { $data = json_decode($request->getContent(), true); + if (!isset($data['job_id'])) { + return new JsonResponse(['message' => 'Invalid request'], Response::HTTP_BAD_REQUEST); + } + + $action = $data['job_id']; + + if (str_starts_with($action, "CreateAuxiliarFiles_")) { + $this->handleCreateAuxFiles($action, $data); + } elseif (str_starts_with($action, "ExportImage_")) { + $this->handleExportImage($action, $data); + } elseif (str_starts_with($action, "ImportImage_")) { + $this->handleImportImage($action, $data); + } else { + return new JsonResponse(['message' => 'Invalid action'], Response::HTTP_BAD_REQUEST); + } + + return new JsonResponse($data, Response::HTTP_OK); + } + + private function handleCreateAuxFiles(string $action, array $data): void + { $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); $imageUuid = $trace->getInput()['imageUuid']; @@ -40,7 +63,8 @@ class ResponseController extends AbstractController $this->entityManager->persist($trace); $this->entityManager->flush(); - return new JsonResponse(['message' => 'Image not found'], Response::HTTP_NOT_FOUND); + new JsonResponse(['message' => 'Image not found'], Response::HTTP_NOT_FOUND); + return; } $image->setImageFullsum($data['image_id']); @@ -52,8 +76,69 @@ class ResponseController extends AbstractController $this->entityManager->persist($trace); $this->entityManager->flush(); + } - return new JsonResponse($data, Response::HTTP_OK); + private function handleExportImage(string $action, array $data): void + { + $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); + $imageUuid = $trace->getInput()['imageUuid']; + + $image = $this->entityManager->getRepository(Image::class)->findOneBy(['uuid' => $imageUuid]); + + if ($image === null) { + $trace->setStatus(TraceStatus::FAILED); + $trace->setFinishedAt(new \DateTime()); + $trace->setOutput('Image not found'); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + + new JsonResponse(['message' => 'Image not found'], Response::HTTP_NOT_FOUND); + return; + } + + $this->logger->info('Image exported', ['image' => $image->getName()]); + + $image->setStatus(ImageStatus::SUCCESS); + $this->entityManager->persist($image); + + + $trace->setStatus(TraceStatus::SUCCESS); + $trace->setFinishedAt(new \DateTime()); + + $this->entityManager->persist($trace); + $this->entityManager->flush(); + } + + private function handleImportImage(string $action, array $data): void + { + $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); + + $imageUuid = $trace->getInput()['imageUuid']; + + $image = $this->entityManager->getRepository(Image::class)->findOneBy(['uuid' => $imageUuid]); + + if ($image === null) { + $trace->setStatus(TraceStatus::FAILED); + $trace->setFinishedAt(new \DateTime()); + $trace->setOutput('Image not found'); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + + new JsonResponse(['message' => 'Image not found'], Response::HTTP_NOT_FOUND); + return; + } + + $this->logger->info('Image imported', ['image' => $image->getName()]); + + $image->setStatus(ImageStatus::SUCCESS); + $this->entityManager->persist($image); + + + $trace->setStatus(TraceStatus::SUCCESS); + $trace->setFinishedAt(new \DateTime()); + + $this->entityManager->persist($trace); + $this->entityManager->flush(); } } \ No newline at end of file diff --git a/src/Controller/OgRepository/WoLAction.php b/src/Controller/OgRepository/WoLAction.php index 4669dc4..e486651 100644 --- a/src/Controller/OgRepository/WoLAction.php +++ b/src/Controller/OgRepository/WoLAction.php @@ -36,31 +36,35 @@ class WoLAction extends AbstractOgRepositoryController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(WoLInput $input, ImageRepository $repository): JsonResponse + public function __invoke(WoLInput $input): JsonResponse { - /** @var Client $client */ - $client = $input->client->getEntity(); + foreach ($input->clients as $client) { + /** @var Client $client */ + $client = $client->getEntity(); + $repository = $client->getRepository(); + if (!$repository->getIp()) { + throw new ValidatorException('IP is required'); + } - if (!$repository->getIp()) { - throw new ValidatorException('IP is required'); + $params = [ + 'json' => [ + 'broadcast_ip' => '255.255.255.255', + 'mac' => $client->getMac() + ] + ]; + + $this->logger->info('Sending WoL to client', ['mac' => $client->getMac()]); + + $content = $this->createRequest('POST', 'http://'.$repository->getIp(). ':8006/ogrepository/v1/wol', $params); + + $client->setStatus(ClientStatus::INITIALIZING); + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $this->createService->__invoke($client, CommandTypes::SHUTDOWN, TraceStatus::SUCCESS, '', []); } - $params = [ - 'json' => [ - 'broadcast_ip' => '255.255.255.255', - 'mac' => $client->getMac() - ] - ]; - - $content = $this->createRequest('POST', 'http://'.$repository->getIp(). ':8006/ogrepository/v1/wol', $params); - - $client->setStatus(ClientStatus::INITIALIZING); - $this->entityManager->persist($client); - $this->entityManager->flush(); - - $this->createService->__invoke($client, CommandTypes::SHUTDOWN, TraceStatus::SUCCESS, '', []); - - return new JsonResponse(data: $client, status: Response::HTTP_OK); + return new JsonResponse(data: [], status: Response::HTTP_OK); } } diff --git a/src/Dto/Input/ImageInput.php b/src/Dto/Input/ImageInput.php index 1d29283..f270ac9 100644 --- a/src/Dto/Input/ImageInput.php +++ b/src/Dto/Input/ImageInput.php @@ -38,6 +38,10 @@ final class ImageInput #[ApiProperty(description: 'The type of the image', example: "Server")] public ?string $source = 'input'; + #[Groups(['image:write'])] + #[ApiProperty(description: 'The status of the image', example: "PENDING")] + public ?string $status = ImageStatus::PENDING; + #[Groups(['image:write'])] #[ApiProperty(description: 'The software profile of the image')] public ?SoftwareProfileOutput $softwareProfile = null; @@ -73,6 +77,7 @@ final class ImageInput $this->comments = $image->getComments(); $this->type = $image->getType(); $this->remotePc = $image->isRemotePc(); + $this->status = $image->getStatus(); if ($image->getSoftwareProfile()) { $this->softwareProfile = new SoftwareProfileOutput($image->getSoftwareProfile()); @@ -95,13 +100,13 @@ final class ImageInput { if (!$image) { $image = new Image(); + $image->setStatus(ImageStatus::PENDING); } $image->setName($this->name); $image->setDescription($this->description); $image->setComments($this->comments); $image->setType($this->type); - $image->setStatus(ImageStatus::PENDING); if ($this->softwareProfile) { $image->setSoftwareProfile($this->softwareProfile->getEntity()); diff --git a/src/Dto/Input/WoLInput.php b/src/Dto/Input/WoLInput.php index 7ba430c..82d36ca 100644 --- a/src/Dto/Input/WoLInput.php +++ b/src/Dto/Input/WoLInput.php @@ -8,7 +8,10 @@ use Symfony\Component\Serializer\Annotation\Groups; class WoLInput { + /** + * @var ClientOutput[]|null + */ #[Groups(['repository:write'])] #[ApiProperty(description: 'The client to wol')] - public ?ClientOutput $client = null; + public ?array $clients = null; } \ No newline at end of file diff --git a/src/Model/CommandTypes.php b/src/Model/CommandTypes.php index fd2570e..aac478f 100644 --- a/src/Model/CommandTypes.php +++ b/src/Model/CommandTypes.php @@ -8,6 +8,8 @@ final class CommandTypes public const string RESTORE_IMAGE = 'restore-image'; public const string CREATE_IMAGE = 'create-image'; public const string CREATE_IMAGE_AUX_FILE = 'create-image-aux-file'; + public const string IMPORT_IMAGE = 'import-image'; + public const string EXPORT_IMAGE = 'export-image'; public const string POWER_ON = 'power-on'; public const string REBOOT = 'reboot'; public const string SHUTDOWN = 'shutdown'; @@ -20,6 +22,8 @@ final class CommandTypes self::RESTORE_IMAGE => 'Update Cache', self::CREATE_IMAGE => 'Create Image', self::CREATE_IMAGE_AUX_FILE => 'Crear fichero auxiliar en repositorio', + self::IMPORT_IMAGE => 'Importar imagen', + self::EXPORT_IMAGE => 'Exportar imagen', self::POWER_ON => 'Encender', self::REBOOT => 'Reiniciar', self::SHUTDOWN => 'Apagar', diff --git a/src/Model/ImageStatus.php b/src/Model/ImageStatus.php index 3ef24f1..db13d5d 100644 --- a/src/Model/ImageStatus.php +++ b/src/Model/ImageStatus.php @@ -10,6 +10,7 @@ final class ImageStatus public const string SUCCESS = 'success'; public const string TRASH = 'trash'; public const string FAILED = 'failed'; + public const string TRANSFERING = 'transfering'; private const array STATUS = [ self::PENDING => 'Pendiente', @@ -18,6 +19,7 @@ final class ImageStatus self::TRASH => 'Papelera', self::SUCCESS => 'Completado', self::FAILED => 'Fallido', + self::TRANSFERING => 'Transferiendo', ]; public static function getStatus(): array