Merge pull request 'develop' (#43) from develop into main
ogcore-debian-package/pipeline/head This commit looks good Details
testing/ogcore-api/pipeline/head There was a failure building this commit Details

Reviewed-on: #43
develop^2
Manuel Aranda Rosales 2025-08-06 12:38:10 +02:00
commit 63721ab428
35 changed files with 1245 additions and 117 deletions

4
.gitignore vendored
View File

@ -34,3 +34,7 @@ debian/*.log
debian/.debhelper/
debian/files
### Certificates
certs/

View File

@ -1,8 +1,19 @@
# 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

View File

@ -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:

View File

@ -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,25 @@ 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
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:

View File

@ -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:

View File

@ -47,7 +47,7 @@ when@prod:
syslog:
type: syslog
ident: "ogcore"
level: error
level: info
formatter: App\Formatter\CustomLineFormatter
channels: ["!event"]
deprecation:

View File

@ -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];

View File

@ -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;
}
}
}

View File

@ -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());
}
}
}
}

View File

@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace App\Controller\OgAgent;
use App\Dto\Input\CheckPartitionSizesInput;
use App\Entity\Client;
use App\Entity\Command;
use App\Entity\Image;
use App\Entity\Partition;
use App\Entity\Trace;
use App\Model\ClientStatus;
use App\Model\CommandTypes;
use App\Model\ImageStatus;
use App\Model\TraceStatus;
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;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Validator\Exception\ValidatorException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class CheckPartitionSizesAction extends AbstractOgAgentController
{
/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function __invoke(CheckPartitionSizesInput $input, Client $client): JsonResponse
{
$partitions = $input->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);
}
}

View File

@ -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,13 +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();
// 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);
return $this->createGitImage($image, $partitionInfo, $repository, $queue, $gitRepositoryName, $existingTrace);
}
}
@ -99,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'])) {
@ -159,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(),
];
@ -190,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: ['/clients/' . $client->getUuid() => $jobId], status: Response::HTTP_OK);
} catch (Exception $e) {
@ -228,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'])) {
@ -285,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(),
];
@ -315,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: ['/clients/' . $client->getUuid() => $jobId], status: Response::HTTP_OK);
} catch (Exception $e) {

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Controller\OgAgent;
use App\Dto\Input\GetGitDataInput;
use App\Entity\Client;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class GetGitDataAction extends AbstractOgAgentController
{
public function __invoke(GetGitDataInput $input): JsonResponse
{
$data = [
'nfn' => '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);
}
}

View File

@ -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;
@ -127,7 +127,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);
}
$clientJobs['/clients/' . $client->getUuid()] = $jobId;
}

View File

@ -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,7 +31,7 @@ class RunScriptAction extends AbstractOgAgentController
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function __invoke(CommandExecuteInput $input): JsonResponse
public function __invoke(CommandExecuteInput $input, ?Trace $existingTrace = null): JsonResponse
{
$clientJobs = [];
@ -43,51 +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) '/clients/' . $client->getUuid()] = $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: $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();
}
}

View File

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace App\Controller\OgAgent;
use App\Dto\Input\UpdateGitImageInput;
use App\Entity\Client;
use App\Model\ClientStatus;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
class UpdateGitImageAction extends AbstractOgAgentController
{
/**
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function __invoke(UpdateGitImageInput $input, Client $client)
{
if (!$client->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;
}
}

View File

@ -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();

View File

@ -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_ActualizarImagenGit';
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']]);

View File

@ -0,0 +1,36 @@
<?php
namespace App\Controller\OgRepository\Git;
use App\Controller\OgRepository\AbstractOgRepositoryController;
use App\Dto\Input\CreateBranchInput;
use App\Entity\ImageRepository;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Validator\Exception\ValidatorException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
#[AsController]
class CreateBranchAction extends AbstractOgRepositoryController
{
public function __invoke(ImageRepository $repository, CreateBranchInput $input): JsonResponse
{
$content = $this->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);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Controller\OgRepository\Git;
use App\Controller\OgRepository\AbstractOgRepositoryController;
use App\Dto\Input\CreateTagInput;
use App\Entity\ImageRepository;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Validator\Exception\ValidatorException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
#[AsController]
class CreateTagAction extends AbstractOgRepositoryController
{
public function __invoke(ImageRepository $repository, CreateTagInput $input): JsonResponse
{
$content = $this->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);
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use ApiPlatform\Validator\ValidatorInterface;
use App\Dto\Input\UpdateGitImageInput;
use App\Entity\Client;
use App\Model\CommandTypes;
use App\Model\DeployMethodTypes;
use App\Model\TraceStatus;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
class UpdateGitImageAction extends AbstractController
{
public function __construct(
protected readonly EntityManagerInterface $entityManager,
protected readonly CreateService $createService,
protected readonly ValidatorInterface $validator,
public readonly \App\Controller\OgAgent\UpdateGitImageAction $updateGitImageOgAgentAction,
) {
}
/**
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
* @throws ServerExceptionInterface
*/
public function __invoke(UpdateGitImageInput $input): JsonResponse
{
$this->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,
];
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Dto\Input;
use App\Dto\Output\ClientOutput;
use Symfony\Component\Serializer\Annotation\Groups;
final class CheckPartitionSizesInput
{
/**
* @var PartitionInput[]
*/
#[Groups(['client:write'])]
public array $partitions = [];
}

View File

@ -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);

View File

@ -0,0 +1,21 @@
<?php
namespace App\Dto\Input;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
class CreateBranchInput
{
#[Assert\NotNull]
#[Groups(['repository:write'])]
public ?string $repository = null;
#[Assert\NotNull]
#[Groups(['repository:write'])]
public ?string $commit = null;
#[Assert\NotNull]
#[Groups(['repository:write'])]
public ?string $name = null;
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Dto\Input;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
class CreateTagInput
{
#[Assert\NotNull]
#[Groups(['repository:write'])]
public ?string $repository = null;
#[Assert\NotNull]
#[Groups(['repository:write'])]
public ?string $commit = null;
#[Assert\NotNull]
#[Groups(['repository:write'])]
public ?string $message = null;
#[Assert\NotNull]
#[Groups(['repository:write'])]
public ?string $name = null;
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Dto\Input;
use App\Dto\Output\ClientOutput;
use App\Dto\Output\PartitionOutput;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
class GetGitDataInput
{
#[Assert\NotNull]
#[Groups(['git-repository:write'])]
public ?PartitionOutput $partition = null;
#[Assert\NotNull]
#[Groups(['git-repository:write'])]
public ?ClientOutput $client = null;
}

View File

@ -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 = '';

View File

@ -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;

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Dto\Input;
use ApiPlatform\Metadata\ApiProperty;
use App\Dto\Output\ClientOutput;
use App\Dto\Output\PartitionOutput;
use Symfony\Component\Serializer\Annotation\Groups;
class UpdateGitImageInput
{
#[Groups(['git-repository:write'])]
#[ApiProperty(description: 'The client of the image')]
public ?ClientOutput $client = null;
#[Groups(['git-repository:write'])]
#[ApiProperty(description: 'The client of the image')]
public ?PartitionOutput $partition = null;
#[Groups(['git-repository:write'])]
#[ApiProperty(description: 'The name of the Git repository to use for this image', example: "mi-repositorio")]
public ?string $gitRepository = null;
#[Groups(['git-repository:write'])]
#[ApiProperty(description: 'The name of the Git branch to use for this image', example: "main")]
public ?string $originalBranch = null;
#[Groups(['git-repository:write'])]
#[ApiProperty(description: 'The name of the Git branch to use for this image', example: "main")]
public ?string $destinationBranch = null;
#[Groups(['git-repository:write'])]
#[ApiProperty(description: 'Whether to queue the update', example: false)]
public bool $queue = false;
}

View File

@ -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();

View File

@ -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;

View File

@ -8,6 +8,7 @@ final class CommandTypes
public const string RESTORE_IMAGE = 'restore-image';
public const string CREATE_IMAGE = 'create-image';
public const string CREATE_IMAGE_GIT = 'create-image-git';
public const string UPDATE_IMAGE_GIT = 'update-image-git';
public const string CONVERT_IMAGE = 'convert-image';
public const string CREATE_IMAGE_AUX_FILE = 'create-image-aux-file';
public const string BACKUP_IMAGE = 'backup-image';
@ -34,6 +35,7 @@ final class CommandTypes
self::RESTORE_IMAGE => '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',

View File

@ -74,12 +74,12 @@ 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);
}
$this->validator->validate($image);
$this->imageRepository->save($image);
}

View File

@ -495,7 +495,7 @@
<td class="partition-os">{{ partition.operativeSystem ? partition.operativeSystem.name : '-' }}</td>
<td>
{% if partition.operativeSystem %}
<a href="command+output:/opt/opengnsys/scripts/bootOs.py {{ partition.diskNumber }} {{ partition.partitionNumber }}"
<a href="command+output:/opt/opengnsys/scripts/bootOs {{ partition.diskNumber }} {{ partition.partitionNumber }}"
class="partition-boot-btn"
role="button">
<i class="fas fa-play"></i>