From 65ae79d976410d46749090069192e0f2a812ddaf Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 14 Jul 2025 15:01:32 +0200 Subject: [PATCH 01/16] refs #2462. ogGit, updateImage --- config/api_platform/GitRepository.yaml | 11 ++- src/Controller/OgAgent/CreateImageAction.php | 2 - .../OgAgent/UpdateGitImageAction.php | 80 +++++++++++++++++ .../OgAgent/Webhook/StatusController.php | 25 ++++++ src/Controller/UpdateGitImageAction.php | 89 +++++++++++++++++++ src/Dto/Input/ImageInput.php | 4 - src/Dto/Input/UpdateGitImageInput.php | 29 ++++++ src/Model/CommandTypes.php | 2 + src/State/Processor/ImageProcessor.php | 5 +- 9 files changed, 239 insertions(+), 8 deletions(-) create mode 100644 src/Controller/OgAgent/UpdateGitImageAction.php create mode 100644 src/Controller/UpdateGitImageAction.php create mode 100644 src/Dto/Input/UpdateGitImageInput.php diff --git a/config/api_platform/GitRepository.yaml b/config/api_platform/GitRepository.yaml index f4b7cf4..6b0b09c 100644 --- a/config/api_platform/GitRepository.yaml +++ b/config/api_platform/GitRepository.yaml @@ -93,7 +93,7 @@ resources: description: 'ID del ImageRepository' git_deploy_image: - shortName: Git Repository + shortName: Deploy Git Image description: Deploy Git image class: ApiPlatform\Metadata\Post method: POST @@ -101,6 +101,15 @@ resources: uriTemplate: /git-repositories/deploy-image controller: App\Controller\DeployGitImageAction + git_update_image: + shortName: Update Git Image + description: Update Git image + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\UpdateGitImageInput + uriTemplate: /git-repositories/update-image + controller: App\Controller\UpdateGitImageAction + properties: App\Entity\GitRepository: id: diff --git a/src/Controller/OgAgent/CreateImageAction.php b/src/Controller/OgAgent/CreateImageAction.php index 32b96a6..9364fd2 100644 --- a/src/Controller/OgAgent/CreateImageAction.php +++ b/src/Controller/OgAgent/CreateImageAction.php @@ -81,8 +81,6 @@ class CreateImageAction extends AbstractOgAgentController } else { $repository = $image->getClient()->getRepository(); - // Para imágenes Git, no necesitamos crear entidades en la base de datos - // ya que los repositorios Git son datos externos return $this->createGitImage($image, $partitionInfo, $repository, $queue, $gitRepositoryName); } } diff --git a/src/Controller/OgAgent/UpdateGitImageAction.php b/src/Controller/OgAgent/UpdateGitImageAction.php new file mode 100644 index 0000000..1912e97 --- /dev/null +++ b/src/Controller/OgAgent/UpdateGitImageAction.php @@ -0,0 +1,80 @@ +getIp()) { + throw new BadRequestHttpException('IP is required'); + } + + if (!$input->gitRepository) { + throw new BadRequestHttpException('Git repository name is required for Git image update'); + } + + $partition = $input->partition->getEntity(); + $repository = $client->getRepository(); + + $data = [ + 'dsk' => (string) $partition->getDiskNumber(), + 'par' => (string) $partition->getPartitionNumber(), + 'nci' => $input->gitRepository, + 'ipr' => $repository->getIp(), + 'msg' => 'updating git image', + 'nfn' => 'ModificarImagenGit', + 'ids' => '0' + ]; + + $url = 'https://'.$client->getIp().':8000/opengnsys/ModificarImagenGit'; + + $response = $this->createRequest( + method: 'POST', + url: $url, + params: [ + 'json' => $data, + ], + token: $client->getToken(), + ); + + $this->logger->info('Updating Git image', [ + 'repository' => $input->gitRepository, + 'client' => $client->getIp(), + 'disk' => $partition->getDiskNumber(), + 'partition' => $partition->getPartitionNumber() + ]); + + if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + throw new BadRequestHttpException('Error updating Git image'); + } + + $jobId = $response['job_id']; + + $client->setStatus(ClientStatus::BUSY); + $this->entityManager->persist($client); + $this->entityManager->flush(); + + return $jobId; + } +} \ No newline at end of file diff --git a/src/Controller/OgAgent/Webhook/StatusController.php b/src/Controller/OgAgent/Webhook/StatusController.php index 75a998c..d3132f6 100644 --- a/src/Controller/OgAgent/Webhook/StatusController.php +++ b/src/Controller/OgAgent/Webhook/StatusController.php @@ -39,6 +39,7 @@ class StatusController extends AbstractController { const string CREATE_IMAGE = 'RESPUESTA_CrearImagen'; const string CREATE_IMAGE_GIT = 'RESPUESTA_CrearImagenGit'; + const string UPDATE_IMAGE_GIT = 'RESPUESTA_ModificarImagenGit'; const string RESTORE_IMAGE = 'RESPUESTA_RestaurarImagen'; const string RESTORE_IMAGE_GIT = 'RESPUESTA_RestaurarImagenGit'; const string CONFIGURE_IMAGE = 'RESPUESTA_Configurar'; @@ -115,6 +116,30 @@ class StatusController extends AbstractController $this->logger->info('Git image creation completed.', ['job_id' => $data['job_id'], 'success' => $data['res'] === 1]); } + if (isset($data['nfn']) && $data['nfn'] === self::UPDATE_IMAGE_GIT) { + $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); + + if (!$trace) { + $this->logger->error('Trace not found', $data); + return new JsonResponse(['message' => 'Trace not found'], Response::HTTP_NOT_FOUND); + } + + if ($data['res'] === 1) { + $trace->setStatus(TraceStatus::SUCCESS); + $trace->setFinishedAt(new \DateTime()); + } else { + $trace->setStatus(TraceStatus::FAILED); + $trace->setFinishedAt(new \DateTime()); + $trace->setOutput($data['der']); + } + + $client = $trace->getClient(); + $client->setStatus(ClientStatus::OG_LIVE); + $this->entityManager->persist($client); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + $this->logger->info('Git image update completed.', ['job_id' => $data['job_id'], 'success' => $data['res'] === 1]); + } if (isset($data['nfn']) && $data['nfn'] === self::CREATE_IMAGE) { $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); diff --git a/src/Controller/UpdateGitImageAction.php b/src/Controller/UpdateGitImageAction.php new file mode 100644 index 0000000..b8a8e87 --- /dev/null +++ b/src/Controller/UpdateGitImageAction.php @@ -0,0 +1,89 @@ +validator->validate($input); + + if (!$input->client) { + throw new \InvalidArgumentException('Client is required'); + } + + if (!$input->partition) { + throw new \InvalidArgumentException('Partition is required'); + } + + $this->handleGitUpdate($input); + + return new JsonResponse(data: [], status: Response::HTTP_OK); + } + + private function handleGitUpdate(UpdateGitImageInput $input): void + { + $client = $input->client->getEntity(); + $partition = $input->partition->getEntity(); + + $inputData = $this->createInputData($input, $client, $partition); + $this->processUpdate($client, $input, $inputData, DeployMethodTypes::GIT); + } + + private function processUpdate(Client $client, UpdateGitImageInput $input, array $inputData, string $updateType): void + { + $agentJobId = $this->updateGitImageOgAgentAction->__invoke($input, $client); + + if (!$agentJobId) { + if ($input->queue) { + $this->createService->__invoke($client, CommandTypes::UPDATE_IMAGE_GIT, TraceStatus::PENDING, null, $inputData); + } + return; + } + + $this->createService->__invoke($client, CommandTypes::UPDATE_IMAGE_GIT, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); + } + + private function createInputData(UpdateGitImageInput $input, Client $client, $partition): array + { + return [ + 'method' => 'ModificarImagenGit', + 'type' => 'git', + 'client' => $client->getUuid(), + 'diskNumber' => $partition->getDiskNumber(), + 'partitionNumber' => $partition->getPartitionNumber(), + 'repositoryName' => $input->gitRepository, + ]; + } +} \ No newline at end of file diff --git a/src/Dto/Input/ImageInput.php b/src/Dto/Input/ImageInput.php index f472a78..f8f1f60 100644 --- a/src/Dto/Input/ImageInput.php +++ b/src/Dto/Input/ImageInput.php @@ -25,10 +25,6 @@ final class ImageInput #[ApiProperty(description: 'The name of the image', example: "Image 1")] public ?string $name = null; - #[Groups(['image:write'])] - #[ApiProperty(description: 'The type of the image', example: "Server")] - public ?string $source = 'input'; - #[Groups(['image:write'])] #[ApiProperty(description: 'The type of the image', example: "Server")] public ?string $type = ''; diff --git a/src/Dto/Input/UpdateGitImageInput.php b/src/Dto/Input/UpdateGitImageInput.php new file mode 100644 index 0000000..4fa6bf9 --- /dev/null +++ b/src/Dto/Input/UpdateGitImageInput.php @@ -0,0 +1,29 @@ + 'Update Cache', self::CREATE_IMAGE => 'Create Image', self::CREATE_IMAGE_GIT => 'Create Image Git', + self::UPDATE_IMAGE_GIT => 'Update Image Git', self::CONVERT_IMAGE => 'Convert Image', self::CONVERT_IMAGE_TO_VIRTUAL => 'Convert Image to Virtual', self::CREATE_IMAGE_AUX_FILE => 'Create Image Aux File', diff --git a/src/State/Processor/ImageProcessor.php b/src/State/Processor/ImageProcessor.php index 24c94e2..290517a 100644 --- a/src/State/Processor/ImageProcessor.php +++ b/src/State/Processor/ImageProcessor.php @@ -78,7 +78,10 @@ readonly class ImageProcessor implements ProcessorInterface $response = $this->createImageActionController->__invoke($data->queue, $image, null, null, $data->gitRepository); } - $this->validator->validate($image); + if ($data->type !== 'git') { + $this->validator->validate($image); + } + $this->imageRepository->save($image); } -- 2.40.1 From 2b939adc5b686b8ef2f2e1efc6c03391ffa9dceb Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 15 Jul 2025 09:42:47 +0200 Subject: [PATCH 02/16] refs #2212. Import image name fixed --- .../OgRepository/Image/ImportAction.php | 26 +++++++++++++++++-- .../Webhook/ResponseController.php | 1 + 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Controller/OgRepository/Image/ImportAction.php b/src/Controller/OgRepository/Image/ImportAction.php index 6adc2dd..31c501c 100644 --- a/src/Controller/OgRepository/Image/ImportAction.php +++ b/src/Controller/OgRepository/Image/ImportAction.php @@ -58,11 +58,11 @@ class ImportAction extends AbstractOgRepositoryController } $imageImageRepositoryEntity = new ImageImageRepository(); - $imageImageRepositoryEntity->setName($imageEntity->getName().'_v'.$imageImageRepositoryEntity->getVersion() + 1); + $imageImageRepositoryEntity->setName($imageEntity->getName()); $imageImageRepositoryEntity->setStatus(ImageStatus::AUX_FILES_PENDING); $imageImageRepositoryEntity->setImage($imageEntity); $imageImageRepositoryEntity->setRepository($repository); - $imageImageRepositoryEntity->setVersion(1); + $imageImageRepositoryEntity->setVersion($this->extractVersionFromImageName($image)); $this->entityManager->persist($imageImageRepositoryEntity); $this->entityManager->flush(); @@ -90,4 +90,26 @@ class ImportAction extends AbstractOgRepositoryController return new JsonResponse(data: [], status: Response::HTTP_OK); } + + + private function extractVersionFromImageName(string $imageName): int + { + // Buscar patrones como "_v2", "_v3", etc. + if (preg_match('/_v(\d+)$/', $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 diff --git a/src/Controller/OgRepository/Webhook/ResponseController.php b/src/Controller/OgRepository/Webhook/ResponseController.php index 66198fd..b8451f8 100644 --- a/src/Controller/OgRepository/Webhook/ResponseController.php +++ b/src/Controller/OgRepository/Webhook/ResponseController.php @@ -96,6 +96,7 @@ class ResponseController extends AbstractOgRepositoryController $newImageRepo->setImage($image); $newImageRepo->setVersion($originImageImageRepository->getVersion()); $newImageRepo->setRepository($repository); + $newImageRepo->setPartitionInfo($originImageImageRepository->getPartitionInfo()); $newImageRepo->setStatus(ImageStatus::SUCCESS); if ($trace->getInput()['imageImageRepositoryUuid'] ?? false) { -- 2.40.1 From 3731697aa720ce4f048046a801a046b5574135bf Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 4 Aug 2025 09:36:14 +0200 Subject: [PATCH 03/16] refs #2462. ogGit, updateImage --- config/api_platform/GitRepository.yaml | 10 +++++ .../OgRepository/Git/CreateTagAction.php | 37 +++++++++++++++++++ src/Dto/Input/CreateTagInput.php | 25 +++++++++++++ src/Dto/Input/UpdateGitImageInput.php | 8 ++++ 4 files changed, 80 insertions(+) create mode 100644 src/Controller/OgRepository/Git/CreateTagAction.php create mode 100644 src/Dto/Input/CreateTagInput.php diff --git a/config/api_platform/GitRepository.yaml b/config/api_platform/GitRepository.yaml index 6b0b09c..f414141 100644 --- a/config/api_platform/GitRepository.yaml +++ b/config/api_platform/GitRepository.yaml @@ -101,6 +101,7 @@ resources: uriTemplate: /git-repositories/deploy-image controller: App\Controller\DeployGitImageAction + git_update_image: shortName: Update Git Image description: Update Git image @@ -110,6 +111,15 @@ resources: uriTemplate: /git-repositories/update-image controller: App\Controller\UpdateGitImageAction + get_git_data: + shortName: Get Git Data + description: Get Git data + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\GetGitDataInput + uriTemplate: /git-repositories/get-git-data + controller: App\Controller\OgAgent\GetGitDataAction + properties: App\Entity\GitRepository: id: diff --git a/src/Controller/OgRepository/Git/CreateTagAction.php b/src/Controller/OgRepository/Git/CreateTagAction.php new file mode 100644 index 0000000..c8ce78e --- /dev/null +++ b/src/Controller/OgRepository/Git/CreateTagAction.php @@ -0,0 +1,37 @@ +createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/git/repositories/'.$input->repository.'/tags', [ + 'json' => [ + 'commit' => $input->commit, + 'message' => $input->message, + 'name' => $input->name + ] + ]); + + if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + throw new BadRequestHttpException('Error creating tag'); + } + + return new JsonResponse(data: [], status: Response::HTTP_OK); + } +} \ No newline at end of file diff --git a/src/Dto/Input/CreateTagInput.php b/src/Dto/Input/CreateTagInput.php new file mode 100644 index 0000000..2d360ac --- /dev/null +++ b/src/Dto/Input/CreateTagInput.php @@ -0,0 +1,25 @@ + Date: Mon, 4 Aug 2025 09:38:02 +0200 Subject: [PATCH 04/16] refs #2496. Check partition sizes --- .../OgAgent/CheckPartitionSizesAction.php | 118 ++++++++++++++++++ src/Dto/Input/CheckPartitionSizesInput.php | 17 +++ src/Dto/Input/GetGitDataInput.php | 20 +++ 3 files changed, 155 insertions(+) create mode 100644 src/Controller/OgAgent/CheckPartitionSizesAction.php create mode 100644 src/Dto/Input/CheckPartitionSizesInput.php create mode 100644 src/Dto/Input/GetGitDataInput.php diff --git a/src/Controller/OgAgent/CheckPartitionSizesAction.php b/src/Controller/OgAgent/CheckPartitionSizesAction.php new file mode 100644 index 0000000..2131efd --- /dev/null +++ b/src/Controller/OgAgent/CheckPartitionSizesAction.php @@ -0,0 +1,118 @@ +partitions; + + if (empty($partitions)) { + throw new BadRequestHttpException('Partitions is required'); + } + + $this->logger->info('Input received', [ + 'partitions_count' => count($partitions), + 'input_type' => gettype($input), + 'partitions_type' => gettype($partitions) + ]); + + $disks = []; + foreach ($partitions as $index => $partition) { + $diskNumber = $partition->diskNumber; + + $partitionEntity = $this->entityManager->getRepository(Partition::class)->findOneBy([ + 'client' => $client, + 'partitionNumber' => $partition->partitionNumber, + 'diskNumber' => $partition->diskNumber, + ]); + + if ($partitionEntity) { + $partitionEntity->setClient($client); + $this->entityManager->persist($partitionEntity); + } + + if (!isset($disks[$diskNumber])) { + $disks[$diskNumber] = [ + 'diskData' => [], + 'partitionData' => [] + ]; + } + + $disks[$diskNumber]['diskData'] = [ + 'dis' => (string) $diskNumber, + 'tch' => (string) ($partition->size * 1024), + ]; + + $disks[$diskNumber]['partitionData'][] = [ + 'par' => (string) $partition->partitionNumber, + 'cpt' => $partition->partitionCode, + 'sfi' => $partition->filesystem, + 'tam' => (string) (integer) ($partition->size * 1024), + 'ope' => $partition->format ? "1" : "0", + ]; + } + + foreach ($disks as $diskNumber => $diskInfo) { + $data = []; + if (!empty($diskInfo['diskData'])) { + $data[] = $diskInfo['diskData']; + } + $data = array_merge($data, $diskInfo['partitionData']); + + $result = [ + "nfn" => "Configurar", + "dsk" => (string) $diskNumber, + "cfg" => $data, + "check-sizes" => "true", + "ids" => "0" + ]; + + $response = $this->createRequest( + method: 'POST', + url: 'https://'.$client->getIp().':8000/opengnsys/Configurar', + params: [ + 'json' => $result, + ], + token: $client->getToken(), + ); + + } + + return new JsonResponse(data: $response, status: Response::HTTP_OK); + } +} diff --git a/src/Dto/Input/CheckPartitionSizesInput.php b/src/Dto/Input/CheckPartitionSizesInput.php new file mode 100644 index 0000000..f2fd184 --- /dev/null +++ b/src/Dto/Input/CheckPartitionSizesInput.php @@ -0,0 +1,17 @@ + Date: Mon, 4 Aug 2025 09:38:53 +0200 Subject: [PATCH 05/16] refs #2462. ogGit, updateImage --- src/Controller/OgAgent/GetGitDataAction.php | 39 +++++++++++++++++++ .../OgRepository/Git/CreateBranchAction.php | 36 +++++++++++++++++ src/Dto/Input/CreateBranchInput.php | 21 ++++++++++ 3 files changed, 96 insertions(+) create mode 100644 src/Controller/OgAgent/GetGitDataAction.php create mode 100644 src/Controller/OgRepository/Git/CreateBranchAction.php create mode 100644 src/Dto/Input/CreateBranchInput.php diff --git a/src/Controller/OgAgent/GetGitDataAction.php b/src/Controller/OgAgent/GetGitDataAction.php new file mode 100644 index 0000000..97d29e6 --- /dev/null +++ b/src/Controller/OgAgent/GetGitDataAction.php @@ -0,0 +1,39 @@ + 'GetGitData', + 'dsk' => (string) $input->partition->getEntity()->getDiskNumber(), + 'par' => (string) $input->partition->getEntity()->getPartitionNumber(), + ]; + + + $response = $this->createRequest( + method: 'POST', + url: 'https://'.$input->client->getEntity()->getIp().':8000/opengnsys/GetGitData', + params: [ + 'json' => $data, + ], + token: $input->client->getEntity()->getToken(), + ); + + $parsedResponse = [ + 'branch' => $response['branch'] ?? null, + 'repo' => $response['repo'] ?? null, + ]; + + return new JsonResponse(data: $parsedResponse, status: Response::HTTP_OK); + } +} \ No newline at end of file diff --git a/src/Controller/OgRepository/Git/CreateBranchAction.php b/src/Controller/OgRepository/Git/CreateBranchAction.php new file mode 100644 index 0000000..00c8d96 --- /dev/null +++ b/src/Controller/OgRepository/Git/CreateBranchAction.php @@ -0,0 +1,36 @@ +createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/git/repositories/'.$input->repository.'/branches', [ + 'json' => [ + 'name' => $input->name, + 'commit' => $input->commit, + ] + ]); + + if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + throw new BadRequestHttpException('Error creating branch'); + } + + return new JsonResponse(data: [], status: Response::HTTP_OK); + } +} \ No newline at end of file diff --git a/src/Dto/Input/CreateBranchInput.php b/src/Dto/Input/CreateBranchInput.php new file mode 100644 index 0000000..48c4dce --- /dev/null +++ b/src/Dto/Input/CreateBranchInput.php @@ -0,0 +1,21 @@ + Date: Mon, 4 Aug 2025 09:39:35 +0200 Subject: [PATCH 06/16] refs #2496. Check partition sizes --- config/api_platform/Client.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/api_platform/Client.yaml b/config/api_platform/Client.yaml index 80b0ebd..ea0a64a 100644 --- a/config/api_platform/Client.yaml +++ b/config/api_platform/Client.yaml @@ -92,6 +92,13 @@ resources: uriTemplate: /clients/server/power-off controller: App\Controller\OgAgent\PowerOffAction + check_partition_sizes: + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\CheckPartitionSizesInput + uriTemplate: /clients/{uuid}/check-partition-sizes + controller: App\Controller\OgAgent\CheckPartitionSizesAction + properties: App\Entity\Client: -- 2.40.1 From 173f854aa2cfb7584f54ed767cc814ed641c41e9 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 4 Aug 2025 09:40:10 +0200 Subject: [PATCH 07/16] refs #2462. ogGit, updateImage --- config/api_platform/ImageRepository.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/config/api_platform/ImageRepository.yaml b/config/api_platform/ImageRepository.yaml index 20d5f1d..7361a3c 100644 --- a/config/api_platform/ImageRepository.yaml +++ b/config/api_platform/ImageRepository.yaml @@ -102,6 +102,25 @@ resources: uriTemplate: /image-repositories/server/git/{uuid}/branches controller: App\Controller\OgRepository\Git\GetBranchesAction + git_repository_create_tag: + shortName: OgRepository Server + description: Create a tag in a Git repository + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\CreateTagInput + uriTemplate: /image-repositories/server/git/{uuid}/create-tag + controller: App\Controller\OgRepository\Git\CreateTagAction + + git_repository_create_branch: + shortName: OgRepository Server + description: Create a branch in a Git repository + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\CreateBranchInput + uriTemplate: /image-repositories/server/git/{uuid}/create-branch + controller: App\Controller\OgRepository\Git\CreateBranchAction + + properties: App\Entity\ImageRepository: id: -- 2.40.1 From c4870223e9d2fd1056eb2b8f808313773c263d8a Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 4 Aug 2025 09:44:23 +0200 Subject: [PATCH 08/16] refs #2535. Fixed script date --- src/Command/CheckClientAvailability.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/CheckClientAvailability.php b/src/Command/CheckClientAvailability.php index da74d71..c8958a8 100644 --- a/src/Command/CheckClientAvailability.php +++ b/src/Command/CheckClientAvailability.php @@ -33,7 +33,7 @@ class CheckClientAvailability extends Command protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $threshold = (new \DateTime())->modify(' - '.self::THRESHOLD_MINUTES . ' minutes'); + $threshold = (new \DateTimeImmutable('UTC'))->modify(' - '.self::THRESHOLD_MINUTES . ' minutes'); $startQueryTime = microtime(true); $validStatuses = [ClientStatus::OG_LIVE, ClientStatus::WINDOWS, ClientStatus::LINUX, ClientStatus::MACOS, ClientStatus::INITIALIZING, ClientStatus::LINUX_SESSION, ClientStatus::WINDOWS_SESSION]; -- 2.40.1 From f627f0a86d33ca20ee8259a6d677c43309143ad1 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 4 Aug 2025 09:49:07 +0200 Subject: [PATCH 09/16] refs #2585. CommandTask schedule. Update several scripts with new logic --- src/Command/ExecutePendingTracesCommand.php | 279 +++++++++++++++++- .../RunScheduledCommandTasksCommand.php | 174 +++++++++-- src/Controller/OgAgent/CreateImageAction.php | 64 ++-- .../OgAgent/PartitionAssistantAction.php | 12 +- src/Controller/OgAgent/RunScriptAction.php | 139 ++++++--- .../Webhook/AgentSessionController.php | 2 + .../OgAgent/Webhook/StatusController.php | 2 +- src/Dto/Input/CommandTaskInput.php | 14 +- src/Dto/Input/PartitionInput.php | 24 +- src/Dto/Output/CommandTaskOutput.php | 4 +- src/Entity/Client.php | 1 + 11 files changed, 612 insertions(+), 103 deletions(-) diff --git a/src/Command/ExecutePendingTracesCommand.php b/src/Command/ExecutePendingTracesCommand.php index 71fbbd0..84eeed9 100644 --- a/src/Command/ExecutePendingTracesCommand.php +++ b/src/Command/ExecutePendingTracesCommand.php @@ -4,7 +4,21 @@ declare(strict_types=1); namespace App\Command; +use App\Controller\OgAgent\CreateImageAction; +use App\Controller\OgAgent\DeployImageAction; +use App\Controller\OgAgent\PartitionAssistantAction; +use App\Controller\OgAgent\RunScriptAction; +use App\Dto\Input\CommandExecuteInput; +use App\Dto\Input\DeployImageInput; +use App\Dto\Input\PartitionInput; +use App\Dto\Input\PartitionPostInput; +use App\Dto\Output\ClientOutput; +use App\Entity\Client; +use App\Entity\Image; +use App\Entity\ImageImageRepository; +use App\Entity\Partition; use App\Entity\Trace; +use App\Model\CommandTypes; use App\Model\TraceStatus; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Attribute\AsCommand; @@ -17,7 +31,11 @@ use Symfony\Component\Console\Style\SymfonyStyle; class ExecutePendingTracesCommand extends Command { public function __construct( - private readonly EntityManagerInterface $entityManager + private readonly EntityManagerInterface $entityManager, + private readonly CreateImageAction $createImageAction, + private readonly DeployImageAction $deployImageAction, + private readonly PartitionAssistantAction $partitionAssistantAction, + private readonly RunScriptAction $runScriptAction ) { parent::__construct(); } @@ -28,22 +46,271 @@ class ExecutePendingTracesCommand extends Command $startTime = microtime(true); $traces = $this->entityManager->getRepository(Trace::class) - ->findBy(['status' => TraceStatus::PENDING]); + ->createQueryBuilder('t') + ->select('t') + ->where('t.status = :status') + ->setParameter('status', TraceStatus::PENDING) + ->andWhere('t.id IN ( + SELECT MIN(t2.id) + FROM App\Entity\Trace t2 + WHERE t2.status = :status + GROUP BY t2.client + )') + ->orderBy('t.id', 'ASC') + ->getQuery() + ->getResult(); $count = count($traces); - $io->info("Found $count pending traces"); + $io->info("Found $count pending trace(s) - processing the latest one"); + + $processedCount = 0; + $errorCount = 0; foreach ($traces as $trace) { - $trace->setStatus(TraceStatus::IN_PROGRESS); - $this->entityManager->persist($trace); + try { + $io->info("Processing trace {$trace->getUuid()} with command: {$trace->getCommand()}"); + + $result = $this->executeTrace($trace); + + if ($result) { + $processedCount++; + $io->success("Successfully processed trace {$trace->getUuid()}"); + } else { + $errorCount++; + $io->error("Failed to process trace {$trace->getUuid()}"); + } + } catch (\Exception $e) { + $errorCount++; + $io->error("Error processing trace {$trace->getUuid()}: " . $e->getMessage()); + + $trace->setStatus(TraceStatus::FAILED); + $trace->setOutput($e->getMessage()); + $trace->setFinishedAt(new \DateTime()); + $this->entityManager->persist($trace); + } } $this->entityManager->flush(); $executionTime = microtime(true) - $startTime; - $io->success("Updated $count traces to IN_PROGRESS status"); + $io->success("Processed $processedCount traces successfully, $errorCount failed"); $io->note("Execution time: " . round($executionTime, 3) . "s"); return Command::SUCCESS; } + + private function executeTrace(Trace $trace): bool + { + $command = $trace->getCommand(); + $input = $trace->getInput() ?? []; + $client = $trace->getClient(); + + if (!$client) { + throw new \Exception("No client associated with trace"); + } + + $trace->setExecutedAt(new \DateTime()); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + + try { + switch ($command) { + case CommandTypes::CREATE_IMAGE: + return $this->executeCreateImage($trace, $input); + + case CommandTypes::DEPLOY_IMAGE: + return $this->executeDeployImage($trace, $input); + + case CommandTypes::PARTITION_AND_FORMAT: + return $this->executePartitionAssistant($trace, $input); + + case CommandTypes::RUN_SCRIPT: + return $this->executeRunScript($trace, $input); + + default: + throw new \Exception("Unsupported command type: $command"); + } + } catch (\Exception $e) { + $trace->setStatus(TraceStatus::FAILED); + $trace->setOutput($e->getMessage()); + $trace->setFinishedAt(new \DateTime()); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + + throw $e; + } + } + + private function executeCreateImage(Trace $trace, array $input): bool + { + $client = $trace->getClient(); + + if (!isset($input['image'])) { + throw new \Exception("Image UUID not found in trace input"); + } + + $image = $this->entityManager->getRepository(Image::class) + ->findOneBy(['uuid' => $input['image']]); + + if (!$image) { + throw new \Exception("Image not found with UUID: {$input['image']}"); + } + + $partition = null; + if (isset($input['diskNumber']) && isset($input['partitionNumber'])) { + $partition = $this->entityManager->getRepository(Partition::class) + ->findOneBy([ + 'client' => $client, + 'diskNumber' => $input['diskNumber'], + 'partitionNumber' => $input['partitionNumber'] + ]); + + if (!$partition) { + throw new \Exception("Partition not found for client {$client->getUuid()} with disk {$input['diskNumber']} and partition {$input['partitionNumber']}"); + } + } + + try { + $response = $this->createImageAction->__invoke( + queue: false, + image: $image, + partition: $partition, + client: $client, + gitRepositoryName: $input['gitRepositoryName'] ?? null, + existingTrace: $trace + ); + + if ($response->getStatusCode() === 200) { + $trace->setStatus(TraceStatus::SUCCESS); + $trace->setFinishedAt(new \DateTime()); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + return true; + } + + return false; + } catch (\Exception $e) { + + return false; + } + } + + private function executeDeployImage(Trace $trace, array $input): bool + { + $client = $trace->getClient(); + + if (!isset($input['imageImageRepository'])) { + throw new \Exception("ImageImageRepository UUID not found in trace input"); + } + + $imageImageRepository = $this->entityManager->getRepository(ImageImageRepository::class) + ->findOneBy(['uuid' => $input['imageImageRepository']]); + + if (!$imageImageRepository) { + throw new \Exception("ImageImageRepository not found with UUID: {$input['imageImageRepository']}"); + } + + $deployInput = new DeployImageInput(); + $deployInput->method = $input['method'] ?? 'unicast'; + $deployInput->type = $input['type'] ?? 'monolithic'; + $deployInput->diskNumber = $input['diskNumber'] ?? 1; + $deployInput->partitionNumber = $input['partitionNumber'] ?? 1; + $deployInput->mcastMode = $input['mcastMode'] ?? 'duplex'; + $deployInput->mcastSpeed = $input['mcastSpeed'] ?? 100; + $deployInput->mcastPort = $input['mcastPort'] ?? 8000; + $deployInput->mcastIp = $input['mcastIp'] ?? '224.0.0.1'; + $deployInput->maxClients = $input['maxClients'] ?? 10; + $deployInput->maxTime = $input['maxTime'] ?? 3600; + $deployInput->p2pMode = $input['p2pMode'] ?? 'seed'; + $deployInput->p2pTime = $input['p2pTime'] ?? 300; + + $jobId = $this->deployImageAction->__invoke( + imageImageRepository: $imageImageRepository, + input: $deployInput, + client: $client + ); + + $trace->setJobId($jobId); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + + return true; + } + + private function executePartitionAssistant(Trace $trace, array $input): bool + { + $client = $trace->getClient(); + + $partitionInput = new PartitionPostInput(); + $partitionInput->clients = [new ClientOutput($client)]; + + $partitions = []; + $diskNumber = 1; + + foreach ($input as $item) { + if (isset($item['dis'])) { + $diskNumber = (int)$item['dis']; + } elseif (isset($item['par'])) { + $partitionInputObj = new PartitionInput(); + $partitionInputObj->diskNumber = $diskNumber; + $partitionInputObj->partitionNumber = (int)$item['par']; + $partitionInputObj->partitionCode = $item['cpt'] ?? 'LINUX'; + $partitionInputObj->size = (float)($item['tam'] / 1024); + $partitionInputObj->filesystem = $item['sfi'] ?? 'EXT4'; + $partitionInputObj->format = ($item['ope'] ?? '0') === '1'; + $partitions[] = $partitionInputObj; + } + } + + $partitionInput->partitions = $partitions; + $partitionInput->queue = false; + + try { + $response = $this->partitionAssistantAction->__invoke($partitionInput, $trace); + + if ($response->getStatusCode() === 200) { + $trace->setStatus(TraceStatus::SUCCESS); + $trace->setFinishedAt(new \DateTime()); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + return true; + } + + return false; + } catch (\Exception $e) { + + return false; + } + } + + private function executeRunScript(Trace $trace, array $input): bool + { + $client = $trace->getClient(); + + if (!isset($input['script'])) { + throw new \Exception("Script not found in trace input"); + } + + $commandExecuteInput = new CommandExecuteInput(); + $commandExecuteInput->clients = [new ClientOutput($client)]; + $commandExecuteInput->script = $input['script']; + $commandExecuteInput->queue = false; + + try { + $response = $this->runScriptAction->__invoke($commandExecuteInput, $trace); + + if ($response->getStatusCode() === 200) { + $trace->setStatus(TraceStatus::SUCCESS); + $trace->setFinishedAt(new \DateTime()); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + return true; + } + + return false; + } catch (\Exception $e) { + + return false; + } + } } \ No newline at end of file diff --git a/src/Command/RunScheduledCommandTasksCommand.php b/src/Command/RunScheduledCommandTasksCommand.php index 3a5e7c5..2b5c180 100644 --- a/src/Command/RunScheduledCommandTasksCommand.php +++ b/src/Command/RunScheduledCommandTasksCommand.php @@ -4,13 +4,23 @@ declare(strict_types=1); namespace App\Command; +use App\Controller\OgAgent\CreateImageAction; use App\Controller\OgAgent\DeployImageAction; +use App\Controller\OgAgent\PartitionAssistantAction; use App\Controller\OgAgent\RunScriptAction; use App\Dto\Input\CommandExecuteInput; use App\Dto\Input\DeployImageInput; +use App\Dto\Input\PartitionInput; +use App\Dto\Input\PartitionPostInput; use App\Dto\Output\ClientOutput; use App\Entity\CommandTask; +use App\Entity\Image; +use App\Entity\ImageImageRepository; +use App\Entity\Partition; +use App\Entity\Trace; use App\Model\ClientStatus; +use App\Model\CommandTypes; +use App\Model\TraceStatus; use App\Repository\CommandTaskRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Attribute\AsCommand; @@ -27,6 +37,8 @@ class RunScheduledCommandTasksCommand extends Command private readonly EntityManagerInterface $entityManager, private readonly RunScriptAction $runScriptAction, private readonly DeployImageAction $deployImageAction, + private readonly CreateImageAction $createImageAction, + private readonly PartitionAssistantAction $partitionAssistantAction, ) { parent::__construct(); @@ -38,47 +50,39 @@ class RunScheduledCommandTasksCommand extends Command */ protected function execute(InputInterface $input, OutputInterface $output): int { - $now = new \DateTimeImmutable('now +2 hours'); - $now->setTimezone(new \DateTimeZone('Europe/Madrid')); - $nowMinute = $now->format('Y-m-d H:i'); + $now = new \DateTimeImmutable('now'); $tasks = $this->commandTaskRepository->findAll(); foreach ($tasks as $task) { /** @var CommandTask $task */ $nextExecution = $task->getNextExecution(); + if (!$nextExecution) { continue; } - $taskMinute = $nextExecution->format('Y-m-d H:i'); + $difference = $now->getTimestamp() - $nextExecution->getTimestamp(); - if ($taskMinute === $nowMinute) { + $output->writeln("Now: " . $now->format('Y-m-d H:i:s T')); + $output->writeln("NextExecution: " . $nextExecution->format('Y-m-d H:i:s T')); + $output->writeln("Diferencia: $difference segundos para la tarea {$task->getName()}"); + + if (abs($difference) < 30) { $output->writeln("Ejecutando tarea: " . $task->getName()); $scripts = $task->getCommandTaskScripts()->toArray(); usort($scripts, fn($a, $b) => $a->getExecutionOrder() <=> $b->getExecutionOrder()); foreach ($scripts as $script) { - $output->writeln(" - Ejecutando script de tipo {$script->getType()} con orden {$script->getExecutionOrder()}"); + $output->writeln(" - Creando traza para script de tipo {$script->getType()} con orden {$script->getExecutionOrder()}"); - if ($script->getType() === 'run-script') { - $input = new CommandExecuteInput(); - - foreach ($task->getOrganizationalUnit()?->getClients() as $client) { - if ($client->getStatus() !== ClientStatus::OG_LIVE) { - continue; - } - $input->clients[] = new ClientOutput($client); - } - $input->script = $script->getContent(); - - $this->runScriptAction->__invoke($input); - } + $this->createTraceForScript($script, $task, $output); } - $task->setLastExecution(new \DateTime()); + $task->setLastExecution(new \DateTimeImmutable('now')); $task->setNextExecution($task->calculateNextExecutionDate()); + $this->entityManager->persist($task); } } @@ -86,4 +90,134 @@ class RunScheduledCommandTasksCommand extends Command $this->entityManager->flush(); return Command::SUCCESS; } + + private function createTraceForScript($script, CommandTask $task, OutputInterface $output): void + { + $scriptParameters = $script->getParameters(); + + if ($task->getScope() !== 'clients') { + $clients = $task->getOrganizationalUnit()?->getClients(); + } else { + $clients = $task->getClients(); + } + + foreach ($clients as $client) { + $trace = new Trace(); + $trace->setClient($client); + $trace->setStatus(TraceStatus::PENDING); + $trace->setExecutedAt(new \DateTime()); + + switch ($script->getType()) { + case 'run-script': + $trace->setCommand(CommandTypes::RUN_SCRIPT); + $trace->setInput([ + 'script' => $script->getContent() + ]); + break; + + case 'deploy-image': + $trace->setCommand(CommandTypes::DEPLOY_IMAGE); + $trace->setInput([ + 'imageImageRepository' => $scriptParameters['imageImageRepositoryUuid'], + 'method' => $scriptParameters['method'] ?? 'unicast', + 'type' => $scriptParameters['type'] ?? 'monolithic', + 'diskNumber' => $scriptParameters['diskNumber'] ?? 1, + 'partitionNumber' => $scriptParameters['partitionNumber'] ?? 1, + 'mcastMode' => $scriptParameters['mcastMode'] ?? 'duplex', + 'mcastSpeed' => $scriptParameters['mcastSpeed'] ?? 100, + 'mcastPort' => $scriptParameters['mcastPort'] ?? 8000, + 'mcastIp' => $scriptParameters['mcastIp'] ?? '224.0.0.1', + 'maxClients' => $scriptParameters['maxClients'] ?? 10, + 'maxTime' => $scriptParameters['maxTime'] ?? 3600, + 'p2pMode' => $scriptParameters['p2pMode'] ?? 'seed', + 'p2pTime' => $scriptParameters['p2pTime'] ?? 300 + ]); + break; + + case 'create-image': + $trace->setCommand(CommandTypes::CREATE_IMAGE); + $trace->setInput([ + 'image' => $scriptParameters['imageUuid'], + 'diskNumber' => $scriptParameters['diskNumber'] ?? null, + 'partitionNumber' => $scriptParameters['partitionNumber'] ?? null, + 'gitRepositoryName' => $scriptParameters['gitRepositoryName'] ?? null + ]); + break; + + case 'partition-assistant': + $trace->setCommand(CommandTypes::PARTITION_AND_FORMAT); + $trace->setInput($scriptParameters); + break; + + default: + $output->writeln(" - Tipo de script no soportado: {$script->getType()}"); + continue 2; // Salta al siguiente cliente + } + + $this->entityManager->persist($trace); + $output->writeln(" - Traza creada para cliente {$client->getUuid()} con comando {$trace->getCommand()}"); + } + } + + + private function executePartitionAssistant($script, CommandTask $task, OutputInterface $output): void + { + $scriptParameters = $script->getParameters(); + + $output->writeln(" - Debug: Parameters = " . ($scriptParameters ? json_encode($scriptParameters) : 'null')); + + if (!$scriptParameters) { + $output->writeln(" - Error: Parámetros del script vacíos o nulos"); + return; + } + + if (!is_array($scriptParameters)) { + $output->writeln(" - Error: Los parámetros deben ser un array"); + return; + } + + foreach ($task->getOrganizationalUnit()?->getClients() as $client) { + $partitionInput = new PartitionPostInput(); + $partitionInput->clients = [new ClientOutput($client)]; + + $partitions = []; + + foreach ($scriptParameters as $partitionData) { + if (isset($partitionData['removed']) && $partitionData['removed']) { + continue; + } + + if (!isset($partitionData['size']) || $partitionData['size'] <= 0) { + continue; + } + + $partitionInputObj = new PartitionInput(); + $partitionInputObj->diskNumber = $partitionData['diskNumber'] ?? 1; + $partitionInputObj->partitionNumber = $partitionData['partitionNumber'] ?? 1; + $partitionInputObj->partitionCode = $partitionData['partitionCode'] ?? 'LINUX'; + $partitionInputObj->size = (float)($partitionData['size'] / 1024); + $partitionInputObj->filesystem = $partitionData['filesystem'] ?? 'EXT4'; + $partitionInputObj->format = $partitionData['format'] ?? false; + $partitionInputObj->memoryUsage = $partitionData['memoryUsage'] ?? 0; + + $partitions[] = $partitionInputObj; + } + + if (empty($partitions)) { + $output->writeln(" - Warning: No hay particiones válidas para procesar"); + continue; + } + + $partitionInput->partitions = $partitions; + $partitionInput->queue = false; + + try { + $response = $this->partitionAssistantAction->__invoke($partitionInput, null); + + $output->writeln(" - Partition assistant iniciado para cliente {$client->getUuid()}"); + } catch (\Exception $e) { + $output->writeln(" - Error en partition assistant para cliente {$client->getUuid()}: " . $e->getMessage()); + } + } + } } diff --git a/src/Controller/OgAgent/CreateImageAction.php b/src/Controller/OgAgent/CreateImageAction.php index 9364fd2..9af2c24 100644 --- a/src/Controller/OgAgent/CreateImageAction.php +++ b/src/Controller/OgAgent/CreateImageAction.php @@ -42,7 +42,7 @@ class CreateImageAction extends AbstractOgAgentController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(bool $queue, Image $image, ?Partition $partition = null, ?Client $client = null, ?string $gitRepositoryName = null): JsonResponse + public function __invoke(bool $queue, Image $image, ?Partition $partition = null, ?Client $client = null, ?string $gitRepositoryName = null, ?Trace $existingTrace = null): JsonResponse { $client = $client ?? $image->getClient(); @@ -77,11 +77,11 @@ class CreateImageAction extends AbstractOgAgentController $this->entityManager->persist($imageImageRepository); - return $this->createMonolithicImage($imageImageRepository, $partitionInfo, $image, $repository, $client, $queue); + return $this->createMonolithicImage($imageImageRepository, $partitionInfo, $image, $repository, $client, $queue, $existingTrace); } else { $repository = $image->getClient()->getRepository(); - return $this->createGitImage($image, $partitionInfo, $repository, $queue, $gitRepositoryName); + return $this->createGitImage($image, $partitionInfo, $repository, $queue, $gitRepositoryName, $existingTrace); } } @@ -97,7 +97,8 @@ class CreateImageAction extends AbstractOgAgentController Image $image, ImageRepository $repository, ?Client $client = null, - bool $queue = false + bool $queue = false, + ?Trace $existingTrace = null ): JsonResponse { if (!isset($partitionInfo['numDisk'], $partitionInfo['numPartition'], $partitionInfo['partitionCode'], $partitionInfo['filesystem'])) { @@ -157,6 +158,8 @@ class CreateImageAction extends AbstractOgAgentController 'image' => $image->getUuid(), 'partitionCode' => $partitionInfo['partitionCode'], 'partitionType' => $partitionInfo['filesystem'], + 'partitionNumber' => $partitionInfo['numPartition'], + 'diskNumber' => $partitionInfo['numDisk'], 'repository' => $repository->getIp(), 'name' => $image->getName().'_v'.$imageImageRepository->getVersion(), ]; @@ -188,17 +191,27 @@ class CreateImageAction extends AbstractOgAgentController 'image' => $image->getUuid(), 'partitionCode' => $partitionInfo['partitionCode'], 'partitionType' => $partitionInfo['filesystem'], + 'partitionNumber' => $partitionInfo['numPartition'], + 'diskNumber' => $partitionInfo['numDisk'], 'repository' => $repository->getIp(), 'name' => $image->getName().'_v'.$imageImageRepository->getVersion(), ]; - $this->createService->__invoke( - $image->getClient(), - CommandTypes::CREATE_IMAGE, - TraceStatus::IN_PROGRESS, - $jobId, - $inputData - ); + if ($existingTrace) { + $existingTrace->setStatus(TraceStatus::IN_PROGRESS); + $existingTrace->setJobId($jobId); + $existingTrace->setInput($inputData); + $this->entityManager->persist($existingTrace); + $this->entityManager->flush(); + } else { + $this->createService->__invoke( + $image->getClient(), + CommandTypes::CREATE_IMAGE, + TraceStatus::IN_PROGRESS, + $jobId, + $inputData + ); + } return new JsonResponse(data: $image, status: Response::HTTP_OK); } catch (Exception $e) { @@ -226,7 +239,8 @@ class CreateImageAction extends AbstractOgAgentController array $partitionInfo, ImageRepository $repository, bool $queue = false, - ?string $gitRepositoryName = null + ?string $gitRepositoryName = null, + ?Trace $existingTrace = null ): JsonResponse { if (!isset($partitionInfo['numDisk'], $partitionInfo['numPartition'], $partitionInfo['partitionCode'], $partitionInfo['filesystem'])) { @@ -283,6 +297,8 @@ class CreateImageAction extends AbstractOgAgentController 'image' => $image->getUuid(), 'partitionCode' => $partitionInfo['partitionCode'], 'partitionType' => $partitionInfo['filesystem'], + 'partitionNumber' => $partitionInfo['numPartition'], + 'diskNumber' => $partitionInfo['numDisk'], 'repository' => $repository->getIp(), 'name' => $image->getName(), ]; @@ -313,17 +329,27 @@ class CreateImageAction extends AbstractOgAgentController 'image' => $image->getUuid(), 'partitionCode' => $partitionInfo['partitionCode'], 'partitionType' => $partitionInfo['filesystem'], + 'partitionNumber' => $partitionInfo['numPartition'], + 'diskNumber' => $partitionInfo['numDisk'], 'repository' => $repository->getIp(), 'name' => $image->getName(), ]; - $this->createService->__invoke( - $image->getClient(), - CommandTypes::CREATE_IMAGE_GIT, - TraceStatus::IN_PROGRESS, - $jobId, - $inputData - ); + if ($existingTrace) { + $existingTrace->setStatus(TraceStatus::IN_PROGRESS); + $existingTrace->setJobId($jobId); + $existingTrace->setInput($inputData); + $this->entityManager->persist($existingTrace); + $this->entityManager->flush(); + } else { + $this->createService->__invoke( + $image->getClient(), + CommandTypes::CREATE_IMAGE_GIT, + TraceStatus::IN_PROGRESS, + $jobId, + $inputData + ); + } return new JsonResponse(data: $image, status: Response::HTTP_OK); } catch (Exception $e) { diff --git a/src/Controller/OgAgent/PartitionAssistantAction.php b/src/Controller/OgAgent/PartitionAssistantAction.php index 8d54721..37be1e6 100644 --- a/src/Controller/OgAgent/PartitionAssistantAction.php +++ b/src/Controller/OgAgent/PartitionAssistantAction.php @@ -37,7 +37,7 @@ class PartitionAssistantAction extends AbstractOgAgentController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(PartitionPostInput $input): JsonResponse + public function __invoke(PartitionPostInput $input, ?Trace $existingTrace = null): JsonResponse { $partitions = $input->partitions; @@ -125,7 +125,15 @@ class PartitionAssistantAction extends AbstractOgAgentController $this->entityManager->persist($client); $this->entityManager->flush(); - $this->createService->__invoke($client, CommandTypes::PARTITION_AND_FORMAT, TraceStatus::IN_PROGRESS, $jobId, $data); + if ($existingTrace) { + $existingTrace->setStatus(TraceStatus::IN_PROGRESS); + $existingTrace->setJobId($jobId); + $existingTrace->setInput($data); + $this->entityManager->persist($existingTrace); + $this->entityManager->flush(); + } else { + $this->createService->__invoke($client, CommandTypes::PARTITION_AND_FORMAT, TraceStatus::IN_PROGRESS, $jobId, $data); + } } } diff --git a/src/Controller/OgAgent/RunScriptAction.php b/src/Controller/OgAgent/RunScriptAction.php index 16d95ee..15d2405 100644 --- a/src/Controller/OgAgent/RunScriptAction.php +++ b/src/Controller/OgAgent/RunScriptAction.php @@ -5,6 +5,7 @@ namespace App\Controller\OgAgent; use App\Dto\Input\CommandExecuteInput; use App\Dto\Input\MultipleClientsInput; use App\Entity\Client; +use App\Entity\Trace; use App\Model\ClientStatus; use App\Model\CommandTypes; use App\Model\TraceStatus; @@ -30,8 +31,10 @@ class RunScriptAction extends AbstractOgAgentController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(CommandExecuteInput $input): JsonResponse + public function __invoke(CommandExecuteInput $input, ?Trace $existingTrace = null): JsonResponse { + $clientJobs = []; + /** @var Client $clientEntity */ foreach ($input->clients as $clientEntity) { /** @var Client $client */ @@ -41,49 +44,111 @@ class RunScriptAction extends AbstractOgAgentController throw new BadRequestHttpException('IP is required'); } - $data = [ - 'nfn' => 'EjecutarScript', - 'scp' => base64_encode($input->script), - 'ids' => '0' - ]; + $data = $this->buildRequestData($client, $input->script); + $response = $this->executeScript($client, $data); - $response = $this->createRequest( - method: 'POST', - url: 'https://'.$client->getIp().':8000/opengnsys/EjecutarScript', - params: [ - 'json' => $data, - ], - token: $client->getToken(), - ); - - if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { - $this->logger->error('Error running script', ['client' => $client->getId(), 'error' => $response['error']]); - - if ($input->queue) { - $inputData = [ - 'script' => $input->script, - ]; - $this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::PENDING, null, $inputData); - continue; - } - + if ($this->isErrorResponse($response)) { + $this->handleError($client, $input, $response); continue; } - $this->logger->info('Powering off client', ['client' => $client->getId()]); - $jobId = $response['job_id']; + $clientJobs[(string) $client->get] = $jobId; - $this->entityManager->persist($client); - $this->entityManager->flush(); - - $inputData = [ - 'script' => $input->script, - ]; - - $this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::IN_PROGRESS, $jobId, $inputData); + $this->handleSuccess($client, $input, $response, $existingTrace); } - return new JsonResponse(data: [], status: Response::HTTP_OK); + return new JsonResponse(data: $clientJobs, status: Response::HTTP_OK); + } + + private function buildRequestData(Client $client, string $script): array + { + $scriptBase64 = base64_encode($script); + + if ($this->isLinuxOrWindows($client)) { + return [ + 'script' => $scriptBase64, + 'client' => 'false' + ]; + } + + if ($this->isLinuxOrWindowsSession($client)) { + return [ + 'script' => $scriptBase64, + 'client' => 'true' + ]; + } + + return [ + 'nfn' => 'EjecutarScript', + 'scp' => $scriptBase64, + 'ids' => '0' + ]; + } + + private function isLinuxOrWindows(Client $client): bool + { + return $client->getStatus() === ClientStatus::LINUX || $client->getStatus() === ClientStatus::WINDOWS; + } + + private function isLinuxOrWindowsSession(Client $client): bool + { + return $client->getStatus() === ClientStatus::LINUX_SESSION || $client->getStatus() === ClientStatus::WINDOWS_SESSION; + } + + private function executeScript(Client $client, array $data): array + { + return $this->createRequest( + method: 'POST', + url: 'https://'.$client->getIp().':8000/opengnsys/EjecutarScript', + params: [ + 'json' => $data, + ], + token: $client->getToken(), + ); + } + + private function isErrorResponse(array $response): bool + { + return isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR; + } + + private function handleError(Client $client, CommandExecuteInput $input, array $response): void + { + $this->logger->error('Error running script', [ + 'client' => $client->getId(), + 'error' => $response['error'] + ]); + + if ($input->queue) { + $inputData = ['script' => $input->script]; + $this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::PENDING, null, $inputData); + } + } + + private function handleSuccess(Client $client, CommandExecuteInput $input, array $response, ?Trace $existingTrace): void + { + $this->logger->info('Running script on client', ['client' => $client->getId()]); + + $jobId = $response['job_id']; + $inputData = ['script' => $input->script]; + + $this->entityManager->persist($client); + $this->entityManager->flush(); + + if ($existingTrace) { + $this->updateExistingTrace($existingTrace, $jobId, $inputData); + } else { + $this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::IN_PROGRESS, $jobId, $inputData); + } + } + + private function updateExistingTrace(Trace $trace, string $jobId, array $inputData): void + { + $trace->setStatus(TraceStatus::IN_PROGRESS); + $trace->setJobId($jobId); + $trace->setInput($inputData); + $this->entityManager->persist($trace); + $this->entityManager->flush(); } } \ No newline at end of file diff --git a/src/Controller/OgAgent/Webhook/AgentSessionController.php b/src/Controller/OgAgent/Webhook/AgentSessionController.php index 8c3e72a..f1f2073 100644 --- a/src/Controller/OgAgent/Webhook/AgentSessionController.php +++ b/src/Controller/OgAgent/Webhook/AgentSessionController.php @@ -57,6 +57,8 @@ class AgentSessionController extends AbstractController return new JsonResponse(['message' => 'Invalid status'], Response::HTTP_BAD_REQUEST); } + $client->setToken($data['secret'] ?? null); + $this->entityManager->persist($client); $this->entityManager->flush(); diff --git a/src/Controller/OgAgent/Webhook/StatusController.php b/src/Controller/OgAgent/Webhook/StatusController.php index d3132f6..fc8f748 100644 --- a/src/Controller/OgAgent/Webhook/StatusController.php +++ b/src/Controller/OgAgent/Webhook/StatusController.php @@ -39,7 +39,7 @@ class StatusController extends AbstractController { const string CREATE_IMAGE = 'RESPUESTA_CrearImagen'; const string CREATE_IMAGE_GIT = 'RESPUESTA_CrearImagenGit'; - const string UPDATE_IMAGE_GIT = 'RESPUESTA_ModificarImagenGit'; + const string UPDATE_IMAGE_GIT = 'RESPUESTA_ActualizarImagenGit'; const string RESTORE_IMAGE = 'RESPUESTA_RestaurarImagen'; const string RESTORE_IMAGE_GIT = 'RESPUESTA_RestaurarImagenGit'; const string CONFIGURE_IMAGE = 'RESPUESTA_Configurar'; diff --git a/src/Dto/Input/CommandTaskInput.php b/src/Dto/Input/CommandTaskInput.php index 71a2cd1..4ef35ce 100644 --- a/src/Dto/Input/CommandTaskInput.php +++ b/src/Dto/Input/CommandTaskInput.php @@ -69,8 +69,9 @@ final class CommandTaskInput $this->clients[] = new ClientOutput($client); } } - - $this->organizationalUnit = new OrganizationalUnitOutput($commandTask->getOrganizationalUnit()); + if ($commandTask->getOrganizationalUnit()) { + $this->organizationalUnit = new OrganizationalUnitOutput($commandTask->getOrganizationalUnit()); + } $this->notes = $commandTask->getNotes(); $this->scope = $commandTask->getScope(); $this->content = $commandTask->getParameters(); @@ -87,12 +88,15 @@ final class CommandTaskInput $commandTask->setName($this->name); + $clientsToAdd = []; foreach ($this->clients as $client) { $clientsToAdd[] = $client->getEntity(); - } + } - $commandTask->setOrganizationalUnit($this->organizationalUnit->getEntity()); - $commandTask->setClients( $clientsToAdd ?? [] ); + if ($this->organizationalUnit) { + $commandTask->setOrganizationalUnit($this->organizationalUnit->getEntity()); + } + $commandTask->setClients( $clientsToAdd ); $commandTask->setNotes($this->notes); $commandTask->setParameters($this->content); $commandTask->setScope($this->scope); diff --git a/src/Dto/Input/PartitionInput.php b/src/Dto/Input/PartitionInput.php index 0db87e1..5942a3b 100644 --- a/src/Dto/Input/PartitionInput.php +++ b/src/Dto/Input/PartitionInput.php @@ -16,53 +16,53 @@ use Symfony\Component\Validator\Constraints as Assert; final class PartitionInput { - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] public ?UuidInterface $uuid = null; #[Groups(['partition:write'])] public ?bool $removed = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] public ?bool $format = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The disk number of the partition', example: 1)] public ?int $diskNumber = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The number of the partition', example: 1)] public ?int $partitionNumber = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The code of the partition', example: "code")] public ?string $partitionCode = null; #[Assert\NotNull()] - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The size of the partition', example: 100)] public ?float $size = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The cache content of the partition', example: "cache content")] public ?string $cacheContent = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The type of the partition', example: "LINUX")] public ?string $type = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The filesystem of the partition', example: "EXT4")] public ?string $filesystem = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The operative system name of the partition', example: "Ubuntu")] public ?OperativeSystemOutput $operativeSystem = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The memory usage of the partition', example: 100)] public ?int $memoryUsage = null; - #[Groups(['partition:write'])] + #[Groups(['partition:write', 'client:write'])] #[ApiProperty(description: 'The image of the partition')] public ?ImageOutput $image = null; diff --git a/src/Dto/Output/CommandTaskOutput.php b/src/Dto/Output/CommandTaskOutput.php index b687016..c6be84f 100644 --- a/src/Dto/Output/CommandTaskOutput.php +++ b/src/Dto/Output/CommandTaskOutput.php @@ -50,7 +50,9 @@ final class CommandTaskOutput extends AbstractOutput fn(Client $client) => new ClientOutput($client) )->toArray(); - $this->organizationalUnit = new OrganizationalUnitOutput($commandTask->getOrganizationalUnit()); + if ($commandTask->getOrganizationalUnit()) { + $this->organizationalUnit = new OrganizationalUnitOutput($commandTask->getOrganizationalUnit()); + } $this->notes = $commandTask->getNotes(); $this->scope = $commandTask->getScope(); $this->lastExecution = $commandTask->getLastExecution(); diff --git a/src/Entity/Client.php b/src/Entity/Client.php index 889d9c2..b28f14f 100644 --- a/src/Entity/Client.php +++ b/src/Entity/Client.php @@ -73,6 +73,7 @@ class Client extends AbstractEntity #[ORM\ManyToOne(inversedBy: 'clients')] #[ORM\JoinColumn( onDelete: 'SET NULL')] private ?Subnet $subnet = null; + #[ORM\ManyToOne(inversedBy: 'clients')] #[ORM\JoinColumn( onDelete: 'SET NULL')] private ?OgLive $ogLive = null; -- 2.40.1 From d37e93db6f67a42cd6f69d9392239a5356ab2191 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 4 Aug 2025 09:50:45 +0200 Subject: [PATCH 10/16] refs #2587. Update logs to info --- config/packages/monolog.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml index c5b6aba..c870690 100644 --- a/config/packages/monolog.yaml +++ b/config/packages/monolog.yaml @@ -47,7 +47,7 @@ when@prod: syslog: type: syslog ident: "ogcore" - level: error + level: info formatter: App\Formatter\CustomLineFormatter channels: ["!event"] deprecation: -- 2.40.1 From c93cddeb6212353997251fca058dcacb764f6947 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 4 Aug 2025 09:51:21 +0200 Subject: [PATCH 11/16] refs #2462. ogGit, updateImage --- src/State/Processor/ImageProcessor.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/State/Processor/ImageProcessor.php b/src/State/Processor/ImageProcessor.php index 290517a..eb76264 100644 --- a/src/State/Processor/ImageProcessor.php +++ b/src/State/Processor/ImageProcessor.php @@ -73,14 +73,11 @@ readonly class ImageProcessor implements ProcessorInterface $response = $this->createImageActionController->__invoke($data->queue, $data->selectedImage->getEntity(), $data->partition->getEntity(), $data->client->getEntity(), $data->gitRepository); } else { $image = $data->createOrUpdateEntity($entity); + $this->validator->validate($image); if ($this->kernel->getEnvironment() !== 'test') { $response = $this->createImageActionController->__invoke($data->queue, $image, null, null, $data->gitRepository); } - - if ($data->type !== 'git') { - $this->validator->validate($image); - } $this->imageRepository->save($image); } -- 2.40.1 From 611e2057a966442f0f7a36c7650006211547de83 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 4 Aug 2025 09:52:32 +0200 Subject: [PATCH 12/16] refs #2589. Update menu template. removed .py --- templates/browser/main.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/browser/main.html.twig b/templates/browser/main.html.twig index 17c515d..df02be7 100644 --- a/templates/browser/main.html.twig +++ b/templates/browser/main.html.twig @@ -495,7 +495,7 @@ {{ partition.operativeSystem ? partition.operativeSystem.name : '-' }} {% if partition.operativeSystem %} - -- 2.40.1 From dd2e3346a737bfe19f05e53fc23192e025b2e508 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 4 Aug 2025 09:59:53 +0200 Subject: [PATCH 13/16] updated gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 0efa7f8..82f4788 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,8 @@ debian/ogcore debian/*.substvars debian/*.log debian/.debhelper/ +debian/files +### Certificates +certs/ -- 2.40.1 From 6adbec0fe9741bda660e4874d4600f1f5ef3b6fb Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 4 Aug 2025 12:34:55 +0200 Subject: [PATCH 14/16] solve conflicts --- etc/cron.d/opengnsys-check-clients | 4 +++- src/State/Processor/ImageProcessor.php | 5 +++-- src/State/Processor/PartitionProcessor.php | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/etc/cron.d/opengnsys-check-clients b/etc/cron.d/opengnsys-check-clients index e762794..28f5e88 100644 --- a/etc/cron.d/opengnsys-check-clients +++ b/etc/cron.d/opengnsys-check-clients @@ -1 +1,3 @@ -*/5 * * * * opengnsys php -d memory_limit=512M /opt/opengnsys/ogcore/api/bin/console opengnsys:check-client-availability >> /opt/opengnsys/ogcore/api/var/log/cron.log 2>&1 +*/2 * * * * opengnsys php -d memory_limit=512M /opt/opengnsys/ogcore/api/bin/console opengnsys:check-client-availability>> /opt/opengnsys/ogcore/api/var/log/cron.log 2>&1 +*/1 * * * * opengnsys php -d memory_limit=512M /opt/opengnsys/ogcore/api/bin/console opengnsys:run-scheduled-command-tasks>> /opt/opengnsys/ogcore/api/var/log/cron.log 2>&1 +*/1 * * * * opengnsys php -d memory_limit=512M /opt/opengnsys/ogcore/api/bin/console opengnsys:execute-pending-traces>> /opt/opengnsys/ogcore/api/var/log/cron.log 2>&1 \ No newline at end of file diff --git a/src/State/Processor/ImageProcessor.php b/src/State/Processor/ImageProcessor.php index 92545bd..b1b9bc1 100644 --- a/src/State/Processor/ImageProcessor.php +++ b/src/State/Processor/ImageProcessor.php @@ -89,8 +89,9 @@ readonly class ImageProcessor implements ProcessorInterface } if ($response instanceof JsonResponse && $response->getStatusCode() === 200) { - $jobContent = json_decode($response->getContent(), true); - return new JsonResponse(data: $jobContent, status: Response::HTTP_OK); + $jobContent = json_decode($response->getContent(), true, 512, JSON_UNESCAPED_SLASHES); + $jsonString = json_encode($jobContent, JSON_UNESCAPED_SLASHES); + return new JsonResponse($jsonString, Response::HTTP_OK, [], true); } return new JsonResponse(data: ['/clients/' . $image->getClient()->getUuid() => ['headers' => []]], status: Response::HTTP_OK); diff --git a/src/State/Processor/PartitionProcessor.php b/src/State/Processor/PartitionProcessor.php index 583061e..d127e4b 100644 --- a/src/State/Processor/PartitionProcessor.php +++ b/src/State/Processor/PartitionProcessor.php @@ -78,8 +78,9 @@ readonly class PartitionProcessor implements ProcessorInterface // Si hay una respuesta exitosa, devolvemos el contenido del job ID if ($response instanceof JsonResponse && $response->getStatusCode() === 200) { - $jobContent = json_decode($response->getContent(), true); - return new JsonResponse(data: $jobContent, status: Response::HTTP_OK); + $jobContent = json_decode($response->getContent(), true, 512, JSON_UNESCAPED_SLASHES); + $jsonString = json_encode($jobContent, JSON_UNESCAPED_SLASHES); + return new JsonResponse($jsonString, Response::HTTP_OK, [], true); } return new JsonResponse('OK', Response::HTTP_NO_CONTENT); -- 2.40.1 From 6ad46024ec21bf14eab66f6992a6548bb70aad1d Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 6 Aug 2025 12:37:25 +0200 Subject: [PATCH 15/16] Updated changelog --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a921ddc..3bb62bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,18 @@ # Changelog -## [0.17.3] - 2025-08-04 +## [0.18.1] - 2025-08-06 ### Improved - Se ha añadido el filtro "jobId" a la entidad Trace. +## [0.18.0] -2025-08-06 +### Added +- Se ha añadido una inetragracin con el agente, que hace que podamos comprobar el tamaño de las particiones, y si son correctas. + +### Improved +- Se ha actualizado la funcionalidad para actualizar imagenes de ogGIt +- Se ha mejorado y actualizado la funcionalidad de creacion de tareas, actualizando el script que se ejecuta cada minuto. +- Se ha eliminado el ".py" del menu browser. +- Mejora en el formato de las fechas (datetime) en la ejecucion de algunos scripts. + --- ## [0.17.2] - 2025-08-04 ### Improved -- 2.40.1 From 1da10e85fda2f69af7a9f18c87dd977afc401520 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 6 Aug 2025 12:37:50 +0200 Subject: [PATCH 16/16] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bb62bf..1e6ca2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Improved - Se ha añadido el filtro "jobId" a la entidad Trace. +--- ## [0.18.0] -2025-08-06 ### Added - Se ha añadido una inetragracin con el agente, que hace que podamos comprobar el tamaño de las particiones, y si son correctas. -- 2.40.1