ogRepo new endpoints. Export/Import image
testing/ogcore-api/pipeline/head This commit looks good Details

pull/20/head
Manuel Aranda Rosales 2025-01-31 13:27:03 +01:00
parent e421a38079
commit 3c7fa26e32
10 changed files with 156 additions and 29 deletions

View File

@ -34,7 +34,7 @@ resources:
class: ApiPlatform\Metadata\Post class: ApiPlatform\Metadata\Post
method: POST method: POST
input: App\Dto\Input\WoLInput input: App\Dto\Input\WoLInput
uriTemplate: /image-repositories/{uuid}/wol uriTemplate: /image-repositories/wol
controller: App\Controller\OgRepository\WoLAction controller: App\Controller\OgRepository\WoLAction
get_collection_images_ogrepository: get_collection_images_ogrepository:

View File

@ -6,6 +6,7 @@ namespace App\Controller\OgRepository;
use App\Service\Trace\CreateService; use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -24,6 +25,7 @@ abstract class AbstractOgRepositoryController extends AbstractController
protected readonly EntityManagerInterface $entityManager, protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient, protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService, protected readonly CreateService $createService,
protected readonly LoggerInterface $logger,
) )
{ {
} }

View File

@ -6,6 +6,9 @@ use App\Controller\OgRepository\AbstractOgRepositoryController;
use App\Dto\Input\ExportImportImageRepositoryInput; use App\Dto\Input\ExportImportImageRepositoryInput;
use App\Entity\Image; use App\Entity\Image;
use App\Entity\ImageRepository; 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\JsonResponse;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController; 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); $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->persist($image);
$this->entityManager->flush(); $this->entityManager->flush();
} }

View File

@ -6,6 +6,9 @@ use App\Controller\OgRepository\AbstractOgRepositoryController;
use App\Dto\Input\ExportImportImageRepositoryInput; use App\Dto\Input\ExportImportImageRepositoryInput;
use App\Entity\Image; use App\Entity\Image;
use App\Entity\ImageRepository; 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\JsonResponse;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController; 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); $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->persist($image);
$this->entityManager->flush(); $this->entityManager->flush();
} }

View File

@ -7,6 +7,7 @@ use App\Entity\Trace;
use App\Model\ImageStatus; use App\Model\ImageStatus;
use App\Model\TraceStatus; use App\Model\TraceStatus;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -18,7 +19,8 @@ use Symfony\Component\Routing\Annotation\Route;
class ResponseController extends AbstractController class ResponseController extends AbstractController
{ {
public function __construct( 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); $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']]); $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]);
$imageUuid = $trace->getInput()['imageUuid']; $imageUuid = $trace->getInput()['imageUuid'];
@ -40,7 +63,8 @@ class ResponseController extends AbstractController
$this->entityManager->persist($trace); $this->entityManager->persist($trace);
$this->entityManager->flush(); $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']); $image->setImageFullsum($data['image_id']);
@ -52,8 +76,69 @@ class ResponseController extends AbstractController
$this->entityManager->persist($trace); $this->entityManager->persist($trace);
$this->entityManager->flush(); $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();
} }
} }

View File

@ -36,31 +36,35 @@ class WoLAction extends AbstractOgRepositoryController
* @throws RedirectionExceptionInterface * @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface * @throws ClientExceptionInterface
*/ */
public function __invoke(WoLInput $input, ImageRepository $repository): JsonResponse public function __invoke(WoLInput $input): JsonResponse
{ {
/** @var Client $client */ foreach ($input->clients as $client) {
$client = $input->client->getEntity(); /** @var Client $client */
$client = $client->getEntity();
$repository = $client->getRepository();
if (!$repository->getIp()) {
throw new ValidatorException('IP is required');
}
if (!$repository->getIp()) { $params = [
throw new ValidatorException('IP is required'); '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 = [ return new JsonResponse(data: [], status: Response::HTTP_OK);
'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);
} }
} }

View File

@ -38,6 +38,10 @@ final class ImageInput
#[ApiProperty(description: 'The type of the image', example: "Server")] #[ApiProperty(description: 'The type of the image', example: "Server")]
public ?string $source = 'input'; public ?string $source = 'input';
#[Groups(['image:write'])]
#[ApiProperty(description: 'The status of the image', example: "PENDING")]
public ?string $status = ImageStatus::PENDING;
#[Groups(['image:write'])] #[Groups(['image:write'])]
#[ApiProperty(description: 'The software profile of the image')] #[ApiProperty(description: 'The software profile of the image')]
public ?SoftwareProfileOutput $softwareProfile = null; public ?SoftwareProfileOutput $softwareProfile = null;
@ -73,6 +77,7 @@ final class ImageInput
$this->comments = $image->getComments(); $this->comments = $image->getComments();
$this->type = $image->getType(); $this->type = $image->getType();
$this->remotePc = $image->isRemotePc(); $this->remotePc = $image->isRemotePc();
$this->status = $image->getStatus();
if ($image->getSoftwareProfile()) { if ($image->getSoftwareProfile()) {
$this->softwareProfile = new SoftwareProfileOutput($image->getSoftwareProfile()); $this->softwareProfile = new SoftwareProfileOutput($image->getSoftwareProfile());
@ -95,13 +100,13 @@ final class ImageInput
{ {
if (!$image) { if (!$image) {
$image = new Image(); $image = new Image();
$image->setStatus(ImageStatus::PENDING);
} }
$image->setName($this->name); $image->setName($this->name);
$image->setDescription($this->description); $image->setDescription($this->description);
$image->setComments($this->comments); $image->setComments($this->comments);
$image->setType($this->type); $image->setType($this->type);
$image->setStatus(ImageStatus::PENDING);
if ($this->softwareProfile) { if ($this->softwareProfile) {
$image->setSoftwareProfile($this->softwareProfile->getEntity()); $image->setSoftwareProfile($this->softwareProfile->getEntity());

View File

@ -8,7 +8,10 @@ use Symfony\Component\Serializer\Annotation\Groups;
class WoLInput class WoLInput
{ {
/**
* @var ClientOutput[]|null
*/
#[Groups(['repository:write'])] #[Groups(['repository:write'])]
#[ApiProperty(description: 'The client to wol')] #[ApiProperty(description: 'The client to wol')]
public ?ClientOutput $client = null; public ?array $clients = null;
} }

View File

@ -8,6 +8,8 @@ final class CommandTypes
public const string RESTORE_IMAGE = 'restore-image'; public const string RESTORE_IMAGE = 'restore-image';
public const string CREATE_IMAGE = 'create-image'; public const string CREATE_IMAGE = 'create-image';
public const string CREATE_IMAGE_AUX_FILE = 'create-image-aux-file'; 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 POWER_ON = 'power-on';
public const string REBOOT = 'reboot'; public const string REBOOT = 'reboot';
public const string SHUTDOWN = 'shutdown'; public const string SHUTDOWN = 'shutdown';
@ -20,6 +22,8 @@ final class CommandTypes
self::RESTORE_IMAGE => 'Update Cache', self::RESTORE_IMAGE => 'Update Cache',
self::CREATE_IMAGE => 'Create Image', self::CREATE_IMAGE => 'Create Image',
self::CREATE_IMAGE_AUX_FILE => 'Crear fichero auxiliar en repositorio', 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::POWER_ON => 'Encender',
self::REBOOT => 'Reiniciar', self::REBOOT => 'Reiniciar',
self::SHUTDOWN => 'Apagar', self::SHUTDOWN => 'Apagar',

View File

@ -10,6 +10,7 @@ final class ImageStatus
public const string SUCCESS = 'success'; public const string SUCCESS = 'success';
public const string TRASH = 'trash'; public const string TRASH = 'trash';
public const string FAILED = 'failed'; public const string FAILED = 'failed';
public const string TRANSFERING = 'transfering';
private const array STATUS = [ private const array STATUS = [
self::PENDING => 'Pendiente', self::PENDING => 'Pendiente',
@ -18,6 +19,7 @@ final class ImageStatus
self::TRASH => 'Papelera', self::TRASH => 'Papelera',
self::SUCCESS => 'Completado', self::SUCCESS => 'Completado',
self::FAILED => 'Fallido', self::FAILED => 'Fallido',
self::TRANSFERING => 'Transferiendo',
]; ];
public static function getStatus(): array public static function getStatus(): array