From af5ad811990f6478dba0b96b2a2e81c545f97605 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 26 Jun 2025 17:01:16 +0200 Subject: [PATCH 1/6] refs #1975. Integration ogGit. Crete image and deploy --- config/api_platform/GitImageRepository.yaml | 50 ------- config/api_platform/GitRepository.yaml | 109 ++++++++++++++ config/api_platform/ImageRepository.yaml | 22 ++- config/services.yaml | 5 - config/services/api_platform.yaml | 26 +++- migrations/Version20250618114502.php | 37 +++++ migrations/Version20250618115400.php | 35 +++++ migrations/Version20250626145458.php | 37 +++++ src/Controller/DeployGitImageAction.php | 88 +++++++++++ .../OgAgent/DeployGitImageAction.php | 91 ++++++++++++ .../Git/CreateRepositoryAction.php | 8 +- .../OgRepository/Git/GetBranchesAction.php | 36 +++++ .../OgRepository/Git/GetCollectionAction.php | 4 + .../OgRepository/Git/GetCommitsAction.php | 36 +++++ ...gsAction.php => GetRepositoriesAction.php} | 14 +- .../{CreateTagAction.php => SshKeyAction.php} | 28 ++-- .../OgRepository/Git/SyncAction.php | 73 --------- src/Dto/Input/DeployGitImageInput.php | 49 ++++++ src/Dto/Input/GetBranchesInput.php | 26 ++++ src/Dto/Input/GetCommitsInput.php | 36 +++++ src/Dto/Input/GitImageRepositoryInput.php | 33 ----- src/Dto/Input/GitRepositoryInput.php | 71 +++++++++ src/Dto/Output/GitImageRepositoryOutput.php | 62 -------- src/Dto/Output/GitRepositoryOutput.php | 101 +++++++++++++ src/Entity/GitImageRepository.php | 134 ----------------- src/Entity/GitRepository.php | 51 +++++++ src/Entity/Image.php | 37 ----- src/Entity/ImageRepository.php | 37 ----- .../GitImageRepositoryRepository.php | 33 ----- src/Repository/GitRepositoryRepository.php | 19 +++ src/Service/ExternalGitRepositoryService.php | 139 ++++++++++++++++++ .../Processor/GitImageRepositoryProcessor.php | 68 --------- .../Processor/GitRepositoryProcessor.php | 113 ++++++++++++++ .../ImageImageRepositoryProcessor.php | 16 +- src/State/Processor/ImageProcessor.php | 38 +++-- .../Provider/GitImageRepositoryProvider.php | 71 --------- src/State/Provider/GitRepositoryProvider.php | 136 +++++++++++++++++ 37 files changed, 1317 insertions(+), 652 deletions(-) delete mode 100644 config/api_platform/GitImageRepository.yaml create mode 100644 config/api_platform/GitRepository.yaml create mode 100644 migrations/Version20250618114502.php create mode 100644 migrations/Version20250618115400.php create mode 100644 migrations/Version20250626145458.php create mode 100644 src/Controller/DeployGitImageAction.php create mode 100644 src/Controller/OgAgent/DeployGitImageAction.php create mode 100644 src/Controller/OgRepository/Git/GetBranchesAction.php create mode 100644 src/Controller/OgRepository/Git/GetCommitsAction.php rename src/Controller/OgRepository/Git/{GetTagsAction.php => GetRepositoriesAction.php} (72%) rename src/Controller/OgRepository/Git/{CreateTagAction.php => SshKeyAction.php} (52%) delete mode 100644 src/Controller/OgRepository/Git/SyncAction.php create mode 100644 src/Dto/Input/DeployGitImageInput.php create mode 100644 src/Dto/Input/GetBranchesInput.php create mode 100644 src/Dto/Input/GetCommitsInput.php delete mode 100644 src/Dto/Input/GitImageRepositoryInput.php create mode 100644 src/Dto/Input/GitRepositoryInput.php delete mode 100644 src/Dto/Output/GitImageRepositoryOutput.php create mode 100644 src/Dto/Output/GitRepositoryOutput.php delete mode 100644 src/Entity/GitImageRepository.php create mode 100644 src/Entity/GitRepository.php delete mode 100644 src/Repository/GitImageRepositoryRepository.php create mode 100644 src/Repository/GitRepositoryRepository.php create mode 100644 src/Service/ExternalGitRepositoryService.php delete mode 100644 src/State/Processor/GitImageRepositoryProcessor.php create mode 100644 src/State/Processor/GitRepositoryProcessor.php delete mode 100644 src/State/Provider/GitImageRepositoryProvider.php create mode 100644 src/State/Provider/GitRepositoryProvider.php diff --git a/config/api_platform/GitImageRepository.yaml b/config/api_platform/GitImageRepository.yaml deleted file mode 100644 index b1997a2..0000000 --- a/config/api_platform/GitImageRepository.yaml +++ /dev/null @@ -1,50 +0,0 @@ -resources: - App\Entity\GitImageRepository: - processor: App\State\Processor\GitImageRepositoryProcessor - input: App\Dto\Input\GitImageRepositoryInput - output: App\Dto\Output\GitImageRepositoryOutput - normalizationContext: - groups: ['default', 'git-image-repository:read'] - denormalizationContext: - groups: ['git-image-repository:write'] - operations: - ApiPlatform\Metadata\GetCollection: - provider: App\State\Provider\GitImageRepositoryProvider - filters: - - 'api_platform.filter.image_image_repository.order' - - 'api_platform.filter.image_image_repository.search' - - ApiPlatform\Metadata\Get: - provider: App\State\Provider\GitImageRepositoryProvider - ApiPlatform\Metadata\Put: - provider: App\State\Provider\GitImageRepositoryProvider - ApiPlatform\Metadata\Patch: - provider: App\State\Provider\GitImageRepositoryProvider - ApiPlatform\Metadata\Post: ~ - ApiPlatform\Metadata\Delete: ~ - - get_image_ogrepository: - shortName: OgRepository Server - description: Get image in OgRepository - class: ApiPlatform\Metadata\Get - method: GET - input: false - uriTemplate: /image-image-repositories/server/{uuid}/get - controller: App\Controller\OgRepository\Image\GetAction - - get_image_tags: - shortName: OgRepository Server - description: Get image tags in OgRepository - class: ApiPlatform\Metadata\Get - method: GET - input: false - uriTemplate: /git-image-repositories/server/{uuid}/get-tags - controller: App\Controller\OgRepository\Git\GetTagsAction - - -properties: - App\Entity\GitImageRepository: - id: - identifier: false - uuid: - identifier: true \ No newline at end of file diff --git a/config/api_platform/GitRepository.yaml b/config/api_platform/GitRepository.yaml new file mode 100644 index 0000000..f4b7cf4 --- /dev/null +++ b/config/api_platform/GitRepository.yaml @@ -0,0 +1,109 @@ +resources: + App\Entity\GitRepository: + processor: App\State\Processor\GitRepositoryProcessor + input: App\Dto\Input\GitRepositoryInput + output: App\Dto\Output\GitRepositoryOutput + normalizationContext: + groups: ['default', 'git-repository:read'] + denormalizationContext: + groups: ['git-repository:write'] + operations: + ApiPlatform\Metadata\GetCollection: + provider: App\State\Provider\GitRepositoryProvider + filters: + - 'api_platform.filter.git_repository.order' + - 'api_platform.filter.git_repository.search' + openapiContext: + summary: 'Obtener lista de repositorios Git' + description: 'Obtiene todos los repositorios Git de un servidor específico' + parameters: + - name: 'repository' + in: 'query' + required: true + schema: + type: 'integer' + description: 'ID del ImageRepository que contiene los repositorios' + - name: 'page' + in: 'query' + schema: + type: 'integer' + default: 1 + description: 'Número de página' + - name: 'limit' + in: 'query' + schema: + type: 'integer' + default: 10 + description: 'Elementos por página' + + ApiPlatform\Metadata\Get: + provider: App\State\Provider\GitRepositoryProvider + openapiContext: + summary: 'Obtener un repositorio Git específico' + description: 'Obtiene un repositorio Git específico por su nombre' + parameters: + - name: 'repository' + in: 'query' + required: true + schema: + type: 'integer' + description: 'ID del ImageRepository' + + ApiPlatform\Metadata\Put: + provider: App\State\Provider\GitRepositoryProvider + openapiContext: + summary: 'Actualizar un repositorio Git' + description: 'Actualiza un repositorio Git existente en el servidor externo' + parameters: + - name: 'repository' + in: 'query' + required: true + schema: + type: 'integer' + description: 'ID del ImageRepository' + + ApiPlatform\Metadata\Patch: + provider: App\State\Provider\GitRepositoryProvider + openapiContext: + summary: 'Actualizar parcialmente un repositorio Git' + description: 'Actualiza parcialmente un repositorio Git existente' + parameters: + - name: 'repository' + in: 'query' + required: true + schema: + type: 'integer' + description: 'ID del ImageRepository' + + ApiPlatform\Metadata\Post: + openapiContext: + summary: 'Crear un nuevo repositorio Git' + description: 'Crea un nuevo repositorio Git en el servidor externo. El ID del ImageRepository debe incluirse en el body del request.' + + ApiPlatform\Metadata\Delete: + openapiContext: + summary: 'Eliminar un repositorio Git' + description: 'Elimina un repositorio Git del servidor externo' + parameters: + - name: 'repository' + in: 'query' + required: true + schema: + type: 'integer' + description: 'ID del ImageRepository' + + git_deploy_image: + shortName: Git Repository + description: Deploy Git image + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\DeployGitImageInput + uriTemplate: /git-repositories/deploy-image + controller: App\Controller\DeployGitImageAction + +properties: + App\Entity\GitRepository: + id: + identifier: false + name: + identifier: true \ No newline at end of file diff --git a/config/api_platform/ImageRepository.yaml b/config/api_platform/ImageRepository.yaml index 346aceb..20d5f1d 100644 --- a/config/api_platform/ImageRepository.yaml +++ b/config/api_platform/ImageRepository.yaml @@ -78,19 +78,29 @@ resources: get_collection_images_oggit: shortName: OgRepository Server description: Get collection of image in OgRepository - class: ApiPlatform\Metadata\Post - method: POST + class: ApiPlatform\Metadata\Get + method: GET input: false uriTemplate: /image-repositories/server/git/{uuid}/get-collection controller: App\Controller\OgRepository\Git\GetCollectionAction - git_image_ogrepository_sync: + git_repository_commits: shortName: OgRepository Server + description: Get commits from a specific branch of a Git repository class: ApiPlatform\Metadata\Post method: POST - input: false - uriTemplate: /image-repositories/server/git/{uuid}/sync - controller: App\Controller\OgRepository\Git\SyncAction + input: App\Dto\Input\GetCommitsInput + uriTemplate: /image-repositories/server/git/{uuid}/commits + controller: App\Controller\OgRepository\Git\GetCommitsAction + + git_repository_branches: + shortName: OgRepository Server + description: Get branches from a Git repository + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\GetBranchesInput + uriTemplate: /image-repositories/server/git/{uuid}/branches + controller: App\Controller\OgRepository\Git\GetBranchesAction properties: App\Entity\ImageRepository: diff --git a/config/services.yaml b/config/services.yaml index 879364b..5ece707 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -166,8 +166,3 @@ services: bind: $collectionProvider: '@api_platform.doctrine.orm.state.collection_provider' $itemProvider: '@api_platform.doctrine.orm.state.item_provider' - - App\State\Provider\GitImageRepositoryProvider: - bind: - $collectionProvider: '@api_platform.doctrine.orm.state.collection_provider' - $itemProvider: '@api_platform.doctrine.orm.state.item_provider' diff --git a/config/services/api_platform.yaml b/config/services/api_platform.yaml index fd70e1d..087e0f9 100644 --- a/config/services/api_platform.yaml +++ b/config/services/api_platform.yaml @@ -298,7 +298,7 @@ services: api_platform.filter.trace.search: parent: 'api_platform.doctrine.orm.search_filter' - arguments: [ { 'id': 'exact', 'command': 'exact', 'client.id': 'exact', status: 'exact' } ] + arguments: [ { 'id': 'exact', 'command': 'exact', 'client.id': 'exact', 'status': 'exact' } ] tags: [ 'api_platform.filter' ] api_platform.filter.trace.order: @@ -347,4 +347,28 @@ services: arguments: [ { 'enabled': ~ } ] tags: [ 'api_platform.filter' ] + api_platform.filter.git_repository.order: + parent: 'api_platform.doctrine.orm.order_filter' + arguments: + $properties: { 'id': ~, 'name': ~ } + $orderParameterName: 'order' + tags: [ 'api_platform.filter' ] + + api_platform.filter.git_repository.search: + parent: 'api_platform.doctrine.orm.search_filter' + arguments: [ { 'id': 'exact', 'name': 'partial', 'description': 'partial' } ] + tags: [ 'api_platform.filter' ] + + api_platform.filter.git_image_repository.order: + parent: 'api_platform.doctrine.orm.order_filter' + arguments: + $properties: { 'id': ~, 'name': ~, 'status': ~, 'tag': ~, 'branch': ~ } + $orderParameterName: 'order' + tags: [ 'api_platform.filter' ] + + api_platform.filter.git_image_repository.search: + parent: 'api_platform.doctrine.orm.search_filter' + arguments: [ { 'id': 'exact', 'name': 'partial', 'status': 'exact', 'tag': 'partial', 'branch': 'partial', 'image.id': 'exact', 'repository.id': 'exact', 'gitRepository.id': 'exact' } ] + tags: [ 'api_platform.filter' ] + diff --git a/migrations/Version20250618114502.php b/migrations/Version20250618114502.php new file mode 100644 index 0000000..98780e3 --- /dev/null +++ b/migrations/Version20250618114502.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE git_repository (id INT AUTO_INCREMENT NOT NULL, uuid CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', migration_id VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, description VARCHAR(255) DEFAULT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_C2B3204AD17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE git_image_repository ADD git_repository_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE git_image_repository ADD CONSTRAINT FK_E6944D5EDCA00B70 FOREIGN KEY (git_repository_id) REFERENCES git_repository (id)'); + $this->addSql('CREATE INDEX IDX_E6944D5EDCA00B70 ON git_image_repository (git_repository_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE git_image_repository DROP FOREIGN KEY FK_E6944D5EDCA00B70'); + $this->addSql('DROP TABLE git_repository'); + $this->addSql('DROP INDEX IDX_E6944D5EDCA00B70 ON git_image_repository'); + $this->addSql('ALTER TABLE git_image_repository DROP git_repository_id'); + } +} diff --git a/migrations/Version20250618115400.php b/migrations/Version20250618115400.php new file mode 100644 index 0000000..604f52a --- /dev/null +++ b/migrations/Version20250618115400.php @@ -0,0 +1,35 @@ +addSql('ALTER TABLE git_repository ADD repository_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE git_repository ADD CONSTRAINT FK_C2B3204A50C9D4F7 FOREIGN KEY (repository_id) REFERENCES image_repository (id)'); + $this->addSql('CREATE INDEX IDX_C2B3204A50C9D4F7 ON git_repository (repository_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE git_repository DROP FOREIGN KEY FK_C2B3204A50C9D4F7'); + $this->addSql('DROP INDEX IDX_C2B3204A50C9D4F7 ON git_repository'); + $this->addSql('ALTER TABLE git_repository DROP repository_id'); + } +} diff --git a/migrations/Version20250626145458.php b/migrations/Version20250626145458.php new file mode 100644 index 0000000..ecacf64 --- /dev/null +++ b/migrations/Version20250626145458.php @@ -0,0 +1,37 @@ +addSql('ALTER TABLE git_image_repository DROP FOREIGN KEY FK_E6944D5EDCA00B70'); + $this->addSql('ALTER TABLE git_image_repository DROP FOREIGN KEY FK_E6944D5E3DA5256D'); + $this->addSql('ALTER TABLE git_image_repository DROP FOREIGN KEY FK_E6944D5E50C9D4F7'); + $this->addSql('DROP TABLE git_image_repository'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE git_image_repository (id INT AUTO_INCREMENT NOT NULL, image_id INT NOT NULL, repository_id INT DEFAULT NULL, git_repository_id INT DEFAULT NULL, uuid CHAR(36) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci` COMMENT \'(DC2Type:uuid)\', migration_id VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, created_by VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, updated_by VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, status VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, branch VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, created TINYINT(1) NOT NULL, name VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, tag VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, version INT DEFAULT NULL, description VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, UNIQUE INDEX UNIQ_E6944D5ED17F50A6 (uuid), INDEX IDX_E6944D5E3DA5256D (image_id), INDEX IDX_E6944D5E50C9D4F7 (repository_id), INDEX IDX_E6944D5EDCA00B70 (git_repository_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' '); + $this->addSql('ALTER TABLE git_image_repository ADD CONSTRAINT FK_E6944D5EDCA00B70 FOREIGN KEY (git_repository_id) REFERENCES git_repository (id)'); + $this->addSql('ALTER TABLE git_image_repository ADD CONSTRAINT FK_E6944D5E3DA5256D FOREIGN KEY (image_id) REFERENCES image (id)'); + $this->addSql('ALTER TABLE git_image_repository ADD CONSTRAINT FK_E6944D5E50C9D4F7 FOREIGN KEY (repository_id) REFERENCES image_repository (id)'); + } +} diff --git a/src/Controller/DeployGitImageAction.php b/src/Controller/DeployGitImageAction.php new file mode 100644 index 0000000..62e1f16 --- /dev/null +++ b/src/Controller/DeployGitImageAction.php @@ -0,0 +1,88 @@ +validator->validate($input); + + $this->handleGitDeployment($input); + + return new JsonResponse(data: [], status: Response::HTTP_OK); + } + + private function handleGitDeployment(DeployGitImageInput $input): void + { + foreach ($input->clients as $client) { + $inputData = $this->createInputData($input, $client->getEntity()); + $this->processDeployment($client->getEntity(), $input, $inputData, DeployMethodTypes::GIT); + } + } + + private function processDeployment($client, DeployGitImageInput $input, array $inputData, string $deployType): void + { + $agentJobId = $this->deployGitImageOgAgentAction->__invoke($input, $client); + + if (!$agentJobId) { + if ($input->queue) { + $this->createService->__invoke($client, CommandTypes::DEPLOY_IMAGE, TraceStatus::PENDING, null, $inputData); + } + return; + } + + $this->createService->__invoke($client, CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); + } + + private function createInputData(DeployGitImageInput $input, $client): array + { + return [ + 'method' => $input->method, + 'client' => $client->getUuid(), + 'hexsha' => $input->hexsha, + 'repositoryName' => $input->repositoryName, + 'branch' => $input->branch, + 'numDisk' => (string) $input->diskNumber, + 'numPartition' => (string) $input->partitionNumber, + 'type' => $input->type, + ]; + } +} \ No newline at end of file diff --git a/src/Controller/OgAgent/DeployGitImageAction.php b/src/Controller/OgAgent/DeployGitImageAction.php new file mode 100644 index 0000000..e9b522d --- /dev/null +++ b/src/Controller/OgAgent/DeployGitImageAction.php @@ -0,0 +1,91 @@ +getIp()) { + throw new BadRequestHttpException('IP is required'); + } + + if (!$input->hexsha && !$input->tag) { + throw new BadRequestHttpException('Either hexsha or tag is required for Git image deployment'); + } + + if (!$input->repositoryName) { + throw new BadRequestHttpException('Repository name is required for Git image deployment'); + } + + if (!$input->branch) { + throw new BadRequestHttpException('Branch is required for Git image deployment'); + } + + $repository = $client->getRepository(); + + $data = [ + 'dsk' => (string) $input->diskNumber, + 'par' => (string) $input->partitionNumber, + 'ifs' => "1", + 'idi' => 1, + 'nci' => $input->repositoryName, + 'ipr' => $repository->getIp(), + 'nfn' => 'RestaurarImagenGit', + 'ptc' => 'git', + 'ids' => '0', + 'ref' => $input->hexsha ?? $input->tag + ]; + + $url = 'https://'.$client->getIp().':8000/opengnsys/RestaurarImagenGit'; + + $response = $this->createRequest( + method: 'POST', + url: $url, + params: [ + 'json' => $data, + ], + token: $client->getToken(), + ); + + $this->logger->info('Deploying Git image', [ + 'repository' => $input->repositoryName, + 'branch' => $input->branch, + 'ref' => $input->hexsha ?? $input->tag, + 'client' => $client->getIp() + ]); + + if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + throw new BadRequestHttpException('Error deploying 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/OgRepository/Git/CreateRepositoryAction.php b/src/Controller/OgRepository/Git/CreateRepositoryAction.php index f90aac8..b1e9d29 100644 --- a/src/Controller/OgRepository/Git/CreateRepositoryAction.php +++ b/src/Controller/OgRepository/Git/CreateRepositoryAction.php @@ -5,6 +5,7 @@ namespace App\Controller\OgRepository\Git; use App\Controller\OgRepository\AbstractOgRepositoryController; use App\Entity\Image; use App\Entity\ImageRepository; +use App\Entity\GitRepository; use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -24,15 +25,16 @@ class CreateRepositoryAction extends AbstractOgRepositoryController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(Image $image, ImageRepository $repository): JsonResponse + public function __invoke(GitRepository $gitRepository): JsonResponse { $params = [ 'json' => [ - 'name' => $image->getName(), + 'name' => $gitRepository->getName(), + 'description' => $gitRepository->getDescription(), ] ]; - $content = $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/git/repositories', $params); + $content = $this->createRequest('POST', 'http://'.$gitRepository->getRepository()->getIp().':8006/ogrepository/v1/git/repositories', $params); if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR ) { throw new BadRequestHttpException('Error creating repository'); diff --git a/src/Controller/OgRepository/Git/GetBranchesAction.php b/src/Controller/OgRepository/Git/GetBranchesAction.php new file mode 100644 index 0000000..7e8694b --- /dev/null +++ b/src/Controller/OgRepository/Git/GetBranchesAction.php @@ -0,0 +1,36 @@ +createRequest('GET', 'http://'.$repository->getIp().':8006/ogrepository/v1/git/repositories/'.$input->repositoryName.'/branches'); + + if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + throw new \Exception('Error obteniendo branches del repositorio'); + } + + return new JsonResponse(data: $content, status: Response::HTTP_OK); + } +} \ No newline at end of file diff --git a/src/Controller/OgRepository/Git/GetCollectionAction.php b/src/Controller/OgRepository/Git/GetCollectionAction.php index 55cc436..1703468 100644 --- a/src/Controller/OgRepository/Git/GetCollectionAction.php +++ b/src/Controller/OgRepository/Git/GetCollectionAction.php @@ -26,6 +26,10 @@ class GetCollectionAction extends AbstractOgRepositoryController { $content = $this->createRequest('GET', 'http://'.$data->getIp().':8006/ogrepository/v1/git/repositories'); + if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + throw new \Exception('Error obteniendo commits del repositorio'); + } + return new JsonResponse(data: $content, status: Response::HTTP_OK); } } \ No newline at end of file diff --git a/src/Controller/OgRepository/Git/GetCommitsAction.php b/src/Controller/OgRepository/Git/GetCommitsAction.php new file mode 100644 index 0000000..0818300 --- /dev/null +++ b/src/Controller/OgRepository/Git/GetCommitsAction.php @@ -0,0 +1,36 @@ +createRequest('GET', 'http://'.$repository->getIp().':8006/ogrepository/v1/git/repositories/'.$input->repositoryName.'/branches/'.$input->branch.'/commits'); + + if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + throw new \Exception('Error obteniendo commits del repositorio'); + } + + return new JsonResponse(data: $content, status: Response::HTTP_OK); + } +} \ No newline at end of file diff --git a/src/Controller/OgRepository/Git/GetTagsAction.php b/src/Controller/OgRepository/Git/GetRepositoriesAction.php similarity index 72% rename from src/Controller/OgRepository/Git/GetTagsAction.php rename to src/Controller/OgRepository/Git/GetRepositoriesAction.php index 55a94d3..ac39a75 100644 --- a/src/Controller/OgRepository/Git/GetTagsAction.php +++ b/src/Controller/OgRepository/Git/GetRepositoriesAction.php @@ -3,7 +3,8 @@ namespace App\Controller\OgRepository\Git; use App\Controller\OgRepository\AbstractOgRepositoryController; -use App\Entity\GitImageRepository; +use App\Entity\Image; +use App\Entity\ImageRepository; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; @@ -15,7 +16,7 @@ use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; #[AsController] -class GetTagsAction extends AbstractOgRepositoryController +class GetRepositoriesAction extends AbstractOgRepositoryController { /** * @throws TransportExceptionInterface @@ -23,15 +24,12 @@ class GetTagsAction extends AbstractOgRepositoryController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(GitImageRepository $gitImageRepository): JsonResponse + public function __invoke(ImageRepository $repository): JsonResponse { - $repository = $gitImageRepository->getRepository(); - $image = $gitImageRepository->getImage(); - - $content = $this->createRequest('GET', 'http://'.$repository->getIp().':8006/ogrepository/v1/git/repositories/'.$image->getName().'/tags'); + $content = $this->createRequest('GET', 'http://'.$repository->getIp().':8006/ogrepository/v1/git/repositories'); if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR ) { - throw new BadRequestHttpException('Error getting tags'); + throw new BadRequestHttpException('Error getting repositories'); } return new JsonResponse(data: $content, status: Response::HTTP_OK); diff --git a/src/Controller/OgRepository/Git/CreateTagAction.php b/src/Controller/OgRepository/Git/SshKeyAction.php similarity index 52% rename from src/Controller/OgRepository/Git/CreateTagAction.php rename to src/Controller/OgRepository/Git/SshKeyAction.php index 43c491e..45752f8 100644 --- a/src/Controller/OgRepository/Git/CreateTagAction.php +++ b/src/Controller/OgRepository/Git/SshKeyAction.php @@ -3,13 +3,11 @@ namespace App\Controller\OgRepository\Git; use App\Controller\OgRepository\AbstractOgRepositoryController; -use App\Entity\GitImageRepository; -use App\Entity\Image; +use App\Entity\Client; 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; @@ -17,7 +15,7 @@ use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; #[AsController] -class CreateTagAction extends AbstractOgRepositoryController +class SshKeyAction extends AbstractOgRepositoryController { /** * @throws TransportExceptionInterface @@ -25,23 +23,15 @@ class CreateTagAction extends AbstractOgRepositoryController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(Image $image, ImageRepository $repository, GitImageRepository $gitImageRepository): JsonResponse + public function __invoke(ImageRepository $repository, Client $client): JsonResponse { - $params = [ + $content = $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/git/ssh_key', [ 'json' => [ - 'commit' => "HEAD", - 'message' => $gitImageRepository->getVersion(), - 'name' => $image->getName(), - ] - ]; + 'description' => $client->getName()->getOglive()->getName(), + 'oglive' => $client->getOglive()->getDirectory().'.iso', + ], + ]); - $content = $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/git/repositories/'. - $image->getName().'/tags', $params); - - if (isset($content['error']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR ) { - throw new BadRequestHttpException('Error creating repository'); - } - - return new JsonResponse(data: [], status: Response::HTTP_OK); + return new JsonResponse(data: $content, status: Response::HTTP_OK); } } \ No newline at end of file diff --git a/src/Controller/OgRepository/Git/SyncAction.php b/src/Controller/OgRepository/Git/SyncAction.php deleted file mode 100644 index 1168562..0000000 --- a/src/Controller/OgRepository/Git/SyncAction.php +++ /dev/null @@ -1,73 +0,0 @@ -createRequest('GET', 'http://' . $input->getIp() . ':8006/ogrepository/v1/git/repositories'); - - if (!isset($content['repositories'])) { - return new JsonResponse(data: 'No repositories found', status: Response::HTTP_NOT_FOUND); - } - - $repository = $this->entityManager->getRepository(GitImageRepository::class); - - $existingRepositories = $repository->findBy(['imageRepository' => $input]); - - foreach ($content['repositories'] as $repositoryData) { - $gitImageRepositoryEntity = $repository->findOneBy([ - 'imageRepository' => $input, - 'name' => $repositoryData - ]); - - $imageEntity = $this->entityManager->getRepository(Image::class)->findOneBy(['name' => $repositoryData]); - - if (!$imageEntity) { - $imageEntity = new Image(); - $imageEntity->setName($repositoryData); - $imageEntity->setRemotePc(false); - $imageEntity->setIsGlobal(false); - - $this->entityManager->persist($imageEntity); - } - - if (!$gitImageRepositoryEntity) { - $gitImageRepositoryEntity = new GitImageRepository(); - $gitImageRepositoryEntity->setName($repositoryData); - $gitImageRepositoryEntity->setStatus(ImageStatus::SUCCESS); - $gitImageRepositoryEntity->setImageRepository($input); - $gitImageRepositoryEntity->setImage($imageEntity); - $gitImageRepositoryEntity->setBranch('main'); - $gitImageRepositoryEntity->setCreated(true); - $gitImageRepositoryEntity->setCreatedAt(new \DateTime()); - - $this->entityManager->persist($gitImageRepositoryEntity); - } - } - - $this->entityManager->flush(); - - return new JsonResponse(data: $content, status: Response::HTTP_OK); - } -} \ No newline at end of file diff --git a/src/Dto/Input/DeployGitImageInput.php b/src/Dto/Input/DeployGitImageInput.php new file mode 100644 index 0000000..130190f --- /dev/null +++ b/src/Dto/Input/DeployGitImageInput.php @@ -0,0 +1,49 @@ +description = $gitImageRepository->getDescription(); - } - - public function createOrUpdateEntity(?GitImageRepository $gitImageRepository = null): GitImageRepository - { - if (!$gitImageRepository) { - $gitImageRepository = new GitImageRepository(); - } - - $gitImageRepository->setDescription($this->description); - - return $gitImageRepository; - } -} \ No newline at end of file diff --git a/src/Dto/Input/GitRepositoryInput.php b/src/Dto/Input/GitRepositoryInput.php new file mode 100644 index 0000000..77adc08 --- /dev/null +++ b/src/Dto/Input/GitRepositoryInput.php @@ -0,0 +1,71 @@ +name = $gitRepository->getName(); + $this->description = $gitRepository->getDescription(); + $this->repository = $gitRepository->getRepository()?->getId(); + } + + public function createOrUpdateEntity(?GitRepository $gitRepository = null): GitRepository + { + if (!$gitRepository) { + $gitRepository = new GitRepository(); + } + + $gitRepository->setName($this->name); + $gitRepository->setDescription($this->description); + + return $gitRepository; + } +} \ No newline at end of file diff --git a/src/Dto/Output/GitImageRepositoryOutput.php b/src/Dto/Output/GitImageRepositoryOutput.php deleted file mode 100644 index aeceb17..0000000 --- a/src/Dto/Output/GitImageRepositoryOutput.php +++ /dev/null @@ -1,62 +0,0 @@ -getImage()) { - $this->image = new ImageOutput($gitImageRepository->getImage()); - } - } - - if ($gitImageRepository->getRepository()) { - $this->imageRepository = new ImageRepositoryOutput($gitImageRepository->getRepository()); - } - - $this->name = $gitImageRepository->getName(); - $this->description = $gitImageRepository->getDescription(); - $this->status = $gitImageRepository->getStatus(); - $this->branch = $gitImageRepository->getBranch(); - $this->tag = $gitImageRepository->getTag(); - $this->createdAt = $gitImageRepository->getCreatedAt(); - $this->createdBy = $gitImageRepository->getCreatedBy(); - } -} \ No newline at end of file diff --git a/src/Dto/Output/GitRepositoryOutput.php b/src/Dto/Output/GitRepositoryOutput.php new file mode 100644 index 0000000..9040e0f --- /dev/null +++ b/src/Dto/Output/GitRepositoryOutput.php @@ -0,0 +1,101 @@ +getId()) { + $this->uuid = $gitRepository->getUuid()->toString(); + $this->id = $gitRepository->getId(); + } else { + $this->uuid = $gitRepository->getUuid()->toString(); + $this->id = 0; + } + + $this->name = $gitRepository->getName(); + $this->description = $gitRepository->getDescription(); + + if ($gitRepository->getRepository()) { + $this->repository = new ImageRepositoryOutput($gitRepository->getRepository()); + } + + $this->createdAt = $gitRepository->getCreatedAt() ?: new \DateTime(); + $this->createdBy = $gitRepository->getCreatedBy(); + } +} \ No newline at end of file diff --git a/src/Entity/GitImageRepository.php b/src/Entity/GitImageRepository.php deleted file mode 100644 index 2a56010..0000000 --- a/src/Entity/GitImageRepository.php +++ /dev/null @@ -1,134 +0,0 @@ -image; - } - - public function setImage(?Image $image): static - { - $this->image = $image; - - return $this; - } - - public function getStatus(): ?string - { - return $this->status; - } - - public function setStatus(string $status): static - { - $this->status = $status; - - return $this; - } - - public function getDescription(): ?string - { - return $this->description; - } - - public function setDescription(?string $description): static - { - $this->description = $description; - - return $this; - } - - public function getBranch(): ?string - { - return $this->branch; - } - - public function setBranch(?string $branch): static - { - $this->branch = $branch; - - return $this; - } - - public function getTag(): ?string - { - return $this->tag; - } - - public function setTag(?string $tag): static - { - $this->tag = $tag; - - return $this; - } - - public function getVersion(): ?int - { - return $this->version; - } - - public function setVersion(?int $version): static - { - $this->version = $version; - - return $this; - } - - public function isCreated(): ?bool - { - return $this->created; - } - - public function setCreated(bool $created): static - { - $this->created = $created; - - return $this; - } - - public function getRepository(): ?ImageRepository - { - return $this->repository; - } - - public function setRepository(?ImageRepository $imageRepository): static - { - $this->repository = $imageRepository; - - return $this; - } -} diff --git a/src/Entity/GitRepository.php b/src/Entity/GitRepository.php new file mode 100644 index 0000000..de54128 --- /dev/null +++ b/src/Entity/GitRepository.php @@ -0,0 +1,51 @@ +description; + } + + public function setDescription(?string $description): static + { + $this->description = $description; + + return $this; + } + + public function getRepository(): ?ImageRepository + { + return $this->repository; + } + + public function setRepository(?ImageRepository $repository): static + { + $this->repository = $repository; + + return $this; + } +} diff --git a/src/Entity/Image.php b/src/Entity/Image.php index 5e465cd..24b061e 100644 --- a/src/Entity/Image.php +++ b/src/Entity/Image.php @@ -43,12 +43,6 @@ class Image extends AbstractEntity #[ORM\Column(nullable: true)] private ?int $version = null; - /** - * @var Collection - */ - #[ORM\OneToMany(mappedBy: 'image', targetEntity: GitImageRepository::class,cascade: ['persist'], orphanRemoval: true)] - private Collection $gitImageRepositories; - #[ORM\Column(length: 255)] private ?string $type = null; @@ -56,7 +50,6 @@ class Image extends AbstractEntity { parent::__construct(); $this->imageImageRepositories = new ArrayCollection(); - $this->gitImageRepositories = new ArrayCollection(); } public function getSoftwareProfile(): ?SoftwareProfile @@ -187,36 +180,6 @@ class Image extends AbstractEntity return $this; } - /** - * @return Collection - */ - public function getGitImageRepositories(): Collection - { - return $this->gitImageRepositories; - } - - public function addGitImageRepository(GitImageRepository $gitImageRepository): static - { - if (!$this->gitImageRepositories->contains($gitImageRepository)) { - $this->gitImageRepositories->add($gitImageRepository); - $gitImageRepository->setImage($this); - } - - return $this; - } - - public function removeGitImageRepository(GitImageRepository $gitImageRepository): static - { - if ($this->gitImageRepositories->removeElement($gitImageRepository)) { - // set the owning side to null (unless already changed) - if ($gitImageRepository->getImage() === $this) { - $gitImageRepository->setImage(null); - } - } - - return $this; - } - public function getType(): ?string { return $this->type; diff --git a/src/Entity/ImageRepository.php b/src/Entity/ImageRepository.php index 8be575a..7588190 100644 --- a/src/Entity/ImageRepository.php +++ b/src/Entity/ImageRepository.php @@ -32,17 +32,10 @@ class ImageRepository extends AbstractEntity #[ORM\Column(length: 255, nullable: true)] private ?string $sshPort = null; - /** - * @var Collection - */ - #[ORM\OneToMany(mappedBy: 'repository', targetEntity: GitImageRepository::class)] - private Collection $gitImageRepositories; - public function __construct() { parent::__construct(); $this->imageImageRepositories = new ArrayCollection(); - $this->gitImageRepositories = new ArrayCollection(); } public function getIp(): ?string @@ -122,34 +115,4 @@ class ImageRepository extends AbstractEntity return $this; } - - /** - * @return Collection - */ - public function getGitImageRepositories(): Collection - { - return $this->gitImageRepositories; - } - - public function addGitImageRepository(GitImageRepository $gitImageRepository): static - { - if (!$this->gitImageRepositories->contains($gitImageRepository)) { - $this->gitImageRepositories->add($gitImageRepository); - $gitImageRepository->setImageRepository($this); - } - - return $this; - } - - public function removeGitImageRepository(GitImageRepository $gitImageRepository): static - { - if ($this->gitImageRepositories->removeElement($gitImageRepository)) { - // set the owning side to null (unless already changed) - if ($gitImageRepository->getImageRepository() === $this) { - $gitImageRepository->setImageRepository(null); - } - } - - return $this; - } } diff --git a/src/Repository/GitImageRepositoryRepository.php b/src/Repository/GitImageRepositoryRepository.php deleted file mode 100644 index d9104c7..0000000 --- a/src/Repository/GitImageRepositoryRepository.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ -class GitImageRepositoryRepository extends AbstractRepository -{ - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, GitImageRepository::class); - } - - public function findLatestVersionByImageAndRepository(Image $image, Repository $repository): ?GitImageRepository - { - return $this->createQueryBuilder('i') - ->andWhere('i.image = :imageId') - ->setParameter('imageId', $image->getId()) - ->andWhere('i.repository = :repository') - ->setParameter('repository', $repository->getId()) - ->orderBy('i.version', 'DESC') - ->setMaxResults(1) - ->getQuery() - ->getOneOrNullResult(); - } -} diff --git a/src/Repository/GitRepositoryRepository.php b/src/Repository/GitRepositoryRepository.php new file mode 100644 index 0000000..5cccbb5 --- /dev/null +++ b/src/Repository/GitRepositoryRepository.php @@ -0,0 +1,19 @@ + + */ +class GitRepositoryRepository extends AbstractRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, GitRepository::class); + } + +} diff --git a/src/Service/ExternalGitRepositoryService.php b/src/Service/ExternalGitRepositoryService.php new file mode 100644 index 0000000..20e0962 --- /dev/null +++ b/src/Service/ExternalGitRepositoryService.php @@ -0,0 +1,139 @@ +getCollectionAction->__invoke($imageRepository); + $content = json_decode($response->getContent(), true); + + if ($response->getStatusCode() !== Response::HTTP_OK) { + throw new BadRequestHttpException('Error obteniendo repositorios del servidor externo'); + } + + // El GetCollectionAction devuelve directamente el contenido de la API externa + if (!isset($content['repositories'])) { + return []; + } + + return $content['repositories']; + } catch (\Exception $e) { + throw new BadRequestHttpException('Error obteniendo repositorios: ' . $e->getMessage()); + } + } + + /** + * Obtiene un repositorio específico por nombre + */ + public function getRepository(string $name, ImageRepository $imageRepository): ?array + { + $repositories = $this->getRepositories($imageRepository); + + foreach ($repositories as $repo) { + // La API externa devuelve los nombres como strings simples + if ($repo === $name) { + return ['name' => $repo]; + } + } + + return null; + } + + /** + * Crea un repositorio en el servidor externo + */ + public function createRepository(GitRepository $gitRepository): array + { + try { + $response = $this->createRepositoryAction->__invoke($gitRepository); + + if ($response->getStatusCode() !== Response::HTTP_OK) { + throw new BadRequestHttpException('Error creando repositorio en el servidor externo'); + } + + // El CreateRepositoryAction devuelve un array vacío en caso de éxito + return json_decode($response->getContent(), true) ?: []; + } catch (\Exception $e) { + throw new BadRequestHttpException('Error creando repositorio: ' . $e->getMessage()); + } + } + + /** + * Actualiza un repositorio en el servidor externo + */ + public function updateRepository(GitRepository $gitRepository): array + { + $response = $this->httpClient->request('PUT', + "http://{$gitRepository->getRepository()->getIp()}:8006/ogrepository/v1/git/repositories/{$gitRepository->getName()}", + [ + 'json' => [ + 'name' => $gitRepository->getName(), + 'description' => $gitRepository->getDescription(), + ] + ] + ); + + if ($response->getStatusCode() !== Response::HTTP_OK) { + throw new BadRequestHttpException('Error actualizando repositorio en el servidor externo'); + } + + return $response->toArray(); + } + + /** + * Elimina un repositorio del servidor externo + */ + public function deleteRepository(string $name, ImageRepository $imageRepository): void + { + $response = $this->httpClient->request('DELETE', + "http://{$imageRepository->getIp()}:8006/ogrepository/v1/git/repositories/{$name}" + ); + + if ($response->getStatusCode() !== Response::HTTP_OK) { + throw new BadRequestHttpException('Error eliminando repositorio del servidor externo'); + } + } + + /** + * Convierte datos de la API externa a una entidad GitRepository + */ + public function createEntityFromExternalData($externalData, ImageRepository $imageRepository): GitRepository + { + $gitRepository = new GitRepository(); + + // La API externa devuelve los nombres como strings simples + if (is_string($externalData)) { + $gitRepository->setName($externalData); + } elseif (is_array($externalData) && isset($externalData['name'])) { + $gitRepository->setName($externalData['name']); + $gitRepository->setDescription($externalData['description'] ?? null); + } else { + throw new \InvalidArgumentException('Los datos externos deben ser string o array con nombre'); + } + + $gitRepository->setRepository($imageRepository); + + return $gitRepository; + } +} \ No newline at end of file diff --git a/src/State/Processor/GitImageRepositoryProcessor.php b/src/State/Processor/GitImageRepositoryProcessor.php deleted file mode 100644 index 095838e..0000000 --- a/src/State/Processor/GitImageRepositoryProcessor.php +++ /dev/null @@ -1,68 +0,0 @@ -processCreateOrUpdate($data, $operation, $uriVariables, $context); - case $operation instanceof Delete: - return $this->processDelete($data, $operation, $uriVariables, $context); - } - } - - /** - * @throws \Exception - */ - private function processCreateOrUpdate($data, Operation $operation, array $uriVariables = [], array $context = []): GitImageRepositoryOutput - { - if (!($data instanceof GitImageRepositoryInput)) { - throw new \Exception(sprintf('data is not instance of %s', GitImageRepositoryInput::class)); - } - - $entity = null; - if (isset($uriVariables['uuid'])) { - $entity = $this->imageRepository->findOneByUuid($uriVariables['uuid']); - } - - $image = $data->createOrUpdateEntity($entity); - $this->validator->validate($image); - $this->imageRepository->save($image); - - return new GitImageRepositoryOutput($image); - } - - private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null - { - $user = $this->imageRepository->findOneByUuid($uriVariables['uuid']); - $this->imageRepository->delete($user); - - return null; - } -} diff --git a/src/State/Processor/GitRepositoryProcessor.php b/src/State/Processor/GitRepositoryProcessor.php new file mode 100644 index 0000000..f6bc16f --- /dev/null +++ b/src/State/Processor/GitRepositoryProcessor.php @@ -0,0 +1,113 @@ +processCreateOrUpdate($data, $operation, $uriVariables, $context); + case $operation instanceof Delete: + return $this->processDelete($data, $operation, $uriVariables, $context); + } + } + + /** + * @throws \Exception + */ + private function processCreateOrUpdate($data, Operation $operation, array $uriVariables = [], array $context = []): GitRepositoryOutput + { + if (!($data instanceof GitRepositoryInput)) { + throw new \Exception(sprintf('data is not instance of %s', GitRepositoryInput::class)); + } + + // Obtener el repositoryId del input data + $repositoryId = $data->repository; + + if (!$repositoryId) { + throw new \InvalidArgumentException('El campo "repository" es requerido'); + } + + $imageRepository = $this->imageRepositoryRepository->find($repositoryId); + if (!$imageRepository) { + throw new \Exception('ImageRepository no encontrado'); + } + + $gitRepository = $data->createOrUpdateEntity(); + $gitRepository->setRepository($imageRepository); + + $this->validator->validate($gitRepository); + + if ($this->kernel->getEnvironment() !== 'test') { + try { + if ($operation instanceof Post) { + $this->externalService->createRepository($gitRepository); + } else { + $this->externalService->updateRepository($gitRepository); + } + } catch (\Exception $e) { + throw new \Exception('Error en operación con API externa: ' . $e->getMessage()); + } + } + + return new GitRepositoryOutput($gitRepository); + } + + private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null + { + // Obtener el ImageRepository desde los parámetros + $request = $this->requestStack->getCurrentRequest(); + $repositoryId = $request->query->get('repository'); + $name = $uriVariables['name']; + + if (!$repositoryId || !$name) { + throw new \InvalidArgumentException('Los parámetros "repository" y "name" son requeridos'); + } + + $imageRepository = $this->imageRepositoryRepository->find($repositoryId); + if (!$imageRepository) { + throw new \Exception('ImageRepository no encontrado'); + } + + if ($this->kernel->getEnvironment() !== 'test') { + try { + $this->externalService->deleteRepository($name, $imageRepository); + } catch (\Exception $e) { + throw new \Exception('Error eliminando repositorio en API externa: ' . $e->getMessage()); + } + } + + return null; + } +} \ No newline at end of file diff --git a/src/State/Processor/ImageImageRepositoryProcessor.php b/src/State/Processor/ImageImageRepositoryProcessor.php index d4fed62..c5e53a7 100644 --- a/src/State/Processor/ImageImageRepositoryProcessor.php +++ b/src/State/Processor/ImageImageRepositoryProcessor.php @@ -12,12 +12,14 @@ use ApiPlatform\Validator\ValidatorInterface; use App\Dto\Input\ImageImageRepositoryInput; use App\Dto\Output\ImageImageRepositoryOutput; use App\Repository\ImageImageRepositoryRepository; +use Doctrine\ORM\EntityManagerInterface; readonly class ImageImageRepositoryProcessor implements ProcessorInterface { public function __construct( private ImageImageRepositoryRepository $imageRepository, private ValidatorInterface $validator, + private EntityManagerInterface $entityManager ) { } @@ -60,8 +62,18 @@ readonly class ImageImageRepositoryProcessor implements ProcessorInterface private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null { - $user = $this->imageRepository->findOneByUuid($uriVariables['uuid']); - $this->imageRepository->delete($user); + $imageImageRepository = $this->imageRepository->findOneByUuid($uriVariables['uuid']); + $this->imageRepository->delete($imageImageRepository); + + $repository = $imageImageRepository->getRepository(); + $image = $imageImageRepository->getImage(); + + $imageImageCollection = $image->getImageImageRepositories(); + + if ($imageImageCollection->isEmpty()) { + $this->entityManager->remove($image); + $this->entityManager->flush(); + } return null; } diff --git a/src/State/Processor/ImageProcessor.php b/src/State/Processor/ImageProcessor.php index dbc5302..24c94e2 100644 --- a/src/State/Processor/ImageProcessor.php +++ b/src/State/Processor/ImageProcessor.php @@ -20,6 +20,8 @@ use App\Repository\ImageRepository; use App\Repository\ImageRepositoryRepository as ImageRepositoryRepository; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Component\HttpFoundation\JsonResponse; +use Psr\Log\LoggerInterface; readonly class ImageProcessor implements ProcessorInterface { @@ -28,6 +30,7 @@ readonly class ImageProcessor implements ProcessorInterface private ValidatorInterface $validator, private CreateImageAction $createImageActionController, private KernelInterface $kernel, + private LoggerInterface $logger, ) { } @@ -63,21 +66,36 @@ readonly class ImageProcessor implements ProcessorInterface $entity = $this->imageRepository->findOneByUuid($uriVariables['uuid']); } + try { + $response = null; + $image = null; + if ($data->selectedImage) { + $response = $this->createImageActionController->__invoke($data->queue, $data->selectedImage->getEntity(), $data->partition->getEntity(), $data->client->getEntity(), $data->gitRepository); + } else { + $image = $data->createOrUpdateEntity($entity); - if ($data->selectedImage){ - $response = $this->createImageActionController->__invoke($data->selectedImage->getEntity(), $data->partition->getEntity(), $data->client->getEntity()); - } else { - $image = $data->createOrUpdateEntity($entity); + if ($this->kernel->getEnvironment() !== 'test') { + $response = $this->createImageActionController->__invoke($data->queue, $image, null, null, $data->gitRepository); + } - if ($this->kernel->getEnvironment() !== 'test') { - $response = $this->createImageActionController->__invoke($image); + $this->validator->validate($image); + $this->imageRepository->save($image); } - $this->validator->validate($image); - $this->imageRepository->save($image); - } + if ($response instanceof JsonResponse && $response->getStatusCode() >= 400) { + $content = json_decode($response->getContent(), true); + throw new \Exception($content['error'] ?? 'Error creating image'); + } - return new ImageOutput($data->selectedImage?->getEntity() ?? $image); + return new ImageOutput($data->selectedImage?->getEntity() ?? $image); + } catch (\Exception $e) { + $this->logger->error('Error processing image creation/update', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + throw new \Exception('Error processing image: ' . $e->getMessage()); + } } private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null diff --git a/src/State/Provider/GitImageRepositoryProvider.php b/src/State/Provider/GitImageRepositoryProvider.php deleted file mode 100644 index a13103b..0000000 --- a/src/State/Provider/GitImageRepositoryProvider.php +++ /dev/null @@ -1,71 +0,0 @@ -provideCollection($operation, $uriVariables, $context); - case $operation instanceof Patch: - case $operation instanceof Put: - return $this->provideInput($operation, $uriVariables, $context); - case $operation instanceof Get: - return $this->provideItem($operation, $uriVariables, $context); - } - } - - private function provideCollection(Operation $operation, array $uriVariables = [], array $context = []): object - { - $paginator = $this->collectionProvider->provide($operation, $uriVariables, $context); - - $items = new \ArrayObject(); - foreach ($paginator->getIterator() as $item){ - $items[] = new GitImageRepositoryOutput($item, $context); - } - - return new TraversablePaginator($items, $paginator->getCurrentPage(), $paginator->getItemsPerPage(), $paginator->getTotalItems()); - } - - public function provideItem(Operation $operation, array $uriVariables = [], array $context = []): object|array|null - { - $item = $this->itemProvider->provide($operation, $uriVariables, $context); - - if (!$item) { - throw new NotFoundHttpException('ImageImageRepository not found'); - } - - return new GitImageRepositoryOutput($item); - } - - public function provideInput(Operation $operation, array $uriVariables = [], array $context = []): object|array|null - { - if (isset($uriVariables['uuid'])) { - $item = $this->itemProvider->provide($operation, $uriVariables, $context); - - return $item !== null ? new GitImageRepositoryInput($item) : null; - } - - return new GitImageRepositoryInput(); - } -} diff --git a/src/State/Provider/GitRepositoryProvider.php b/src/State/Provider/GitRepositoryProvider.php new file mode 100644 index 0000000..3f95cbb --- /dev/null +++ b/src/State/Provider/GitRepositoryProvider.php @@ -0,0 +1,136 @@ +provideCollection($operation, $uriVariables, $context); + case $operation instanceof Patch: + case $operation instanceof Put: + return $this->provideInput($operation, $uriVariables, $context); + case $operation instanceof Get: + return $this->provideItem($operation, $uriVariables, $context); + } + } + + private function provideCollection(Operation $operation, array $uriVariables = [], array $context = []): object + { + // Obtener el ImageRepository desde los parámetros de la request + $request = $this->requestStack->getCurrentRequest(); + $repositoryId = $request->query->get('repository'); + + if (!$repositoryId) { + throw new \InvalidArgumentException('El parámetro "repository" es requerido'); + } + + $imageRepository = $this->imageRepositoryRepository->find($repositoryId); + if (!$imageRepository) { + throw new NotFoundHttpException('ImageRepository no encontrado'); + } + + // Obtener repositorios de la API externa + $externalRepositories = $this->externalService->getRepositories($imageRepository); + + $items = new \ArrayObject(); + foreach ($externalRepositories as $externalData) { + $gitRepository = $this->externalService->createEntityFromExternalData($externalData, $imageRepository); + $items[] = new GitRepositoryOutput($gitRepository, $context); + } + + // Simular paginación básica + $page = $request->query->getInt('page', 1); + $limit = $request->query->getInt('limit', 10); + $offset = ($page - 1) * $limit; + + $paginatedItems = array_slice($items->getArrayCopy(), $offset, $limit); + + return new TraversablePaginator( + new \ArrayObject($paginatedItems), + $page, + $limit, + count($items) + ); + } + + public function provideItem(Operation $operation, array $uriVariables = [], array $context = []): object|array|null + { + // Obtener el nombre del repositorio desde los parámetros + $request = $this->requestStack->getCurrentRequest(); + $repositoryId = $request->query->get('repository'); + $name = $uriVariables['name'] ?? $request->query->get('name'); + + if (!$repositoryId || !$name) { + throw new \InvalidArgumentException('Los parámetros "repository" y "name" son requeridos'); + } + + $imageRepository = $this->imageRepositoryRepository->find($repositoryId); + if (!$imageRepository) { + throw new NotFoundHttpException('ImageRepository no encontrado'); + } + + // Obtener repositorio específico de la API externa + $externalData = $this->externalService->getRepository($name, $imageRepository); + + if (!$externalData) { + throw new NotFoundHttpException('GitRepository no encontrado'); + } + + $gitRepository = $this->externalService->createEntityFromExternalData($externalData, $imageRepository); + return new GitRepositoryOutput($gitRepository); + } + + public function provideInput(Operation $operation, array $uriVariables = [], array $context = []): object|array|null + { + if (isset($uriVariables['name'])) { + // Para operaciones de actualización, necesitamos obtener el repositorio existente + $request = $this->requestStack->getCurrentRequest(); + $repositoryId = $request->query->get('repository'); + $name = $uriVariables['name']; + + if (!$repositoryId) { + throw new \InvalidArgumentException('El parámetro "repository" es requerido'); + } + + $imageRepository = $this->imageRepositoryRepository->find($repositoryId); + if (!$imageRepository) { + throw new NotFoundHttpException('ImageRepository no encontrado'); + } + + $externalData = $this->externalService->getRepository($name, $imageRepository); + if (!$externalData) { + throw new NotFoundHttpException('GitRepository no encontrado'); + } + + $gitRepository = $this->externalService->createEntityFromExternalData($externalData, $imageRepository); + return new GitRepositoryInput($gitRepository); + } + + return new GitRepositoryInput(); + } +} \ No newline at end of file From e51e1e13ea1d65e93e81d6494a4157843a16543a Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 26 Jun 2025 17:04:50 +0200 Subject: [PATCH 2/6] refs #1975. Integration ogGit. Crete image and deploy --- .../ChangeOrganizationalUnitAction.php | 9 +- src/Controller/DeployImageAction.php | 197 ++++++----- .../OgAgent/AbstractOgAgentController.php | 3 +- src/Controller/OgAgent/CreateImageAction.php | 305 +++++++++++++----- src/Controller/OgAgent/DeployImageAction.php | 16 +- .../Webhook/AgentSessionController.php | 12 +- .../OgAgent/Webhook/StatusController.php | 35 +- .../AbstractOgRepositoryController.php | 6 +- src/Dto/Input/DeployImageInput.php | 24 +- src/Dto/Input/ImageInput.php | 9 +- src/Model/DeployMethodTypes.php | 2 + 11 files changed, 424 insertions(+), 194 deletions(-) diff --git a/src/Controller/ChangeOrganizationalUnitAction.php b/src/Controller/ChangeOrganizationalUnitAction.php index 66e3ec1..ebf76b5 100644 --- a/src/Controller/ChangeOrganizationalUnitAction.php +++ b/src/Controller/ChangeOrganizationalUnitAction.php @@ -42,7 +42,14 @@ class ChangeOrganizationalUnitAction extends AbstractController $clientEntity->setOrganizationalUnit($organizationalUnit); $this->entityManager->persist($clientEntity); - $this->postAction->__invoke($clientEntity, $clientEntity->getTemplate()); + + $template = $clientEntity->getTemplate() ?? $clientEntity->getOrganizationalUnit()->getNetworkSettings()?->getTemplate(); + + if (!$template) { + throw new BadRequestHttpException('No template found for client'); + } + + $this->postAction->__invoke($clientEntity, $template); } $this->entityManager->flush(); diff --git a/src/Controller/DeployImageAction.php b/src/Controller/DeployImageAction.php index 4d49607..2a6ef6b 100644 --- a/src/Controller/DeployImageAction.php +++ b/src/Controller/DeployImageAction.php @@ -8,6 +8,7 @@ use ApiPlatform\Validator\ValidatorInterface; use App\Dto\Input\DeployImageInput; use App\Entity\Command; use App\Entity\Image; +use App\Model\ClientStatus; use App\Entity\ImageImageRepository; use App\Entity\OrganizationalUnit; use App\Entity\Partition; @@ -31,11 +32,10 @@ class DeployImageAction extends AbstractController protected readonly EntityManagerInterface $entityManager, protected readonly HttpClientInterface $httpClient, protected readonly CreateService $createService, - protected readonly ValidatorInterface $validator, + protected readonly ValidatorInterface $validator, public readonly \App\Controller\OgAgent\DeployImageAction $deployImageOgAgentAction, public readonly \App\Controller\OgRepository\Image\DeployImageAction $deployImageOgRepositoryAction, - ) - { + ) { } /** @@ -47,90 +47,117 @@ class DeployImageAction extends AbstractController { $this->validator->validate($input); - switch ($input->method){ - case DeployMethodTypes::UNICAST: - case DeployMethodTypes::UNICAST_DIRECT: - foreach ($input->clients as $client) { - $inputData = [ - 'method' => $input->method, - 'client' => $client->getEntity()->getUuid(), - 'image' => $image->getUuid(), - 'imageName' => $image->getName(), - 'numDisk' => (string) $input->diskNumber, - 'numPartition' => (string) $input->partitionNumber, - ]; - - $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client->getEntity(), DeployMethodTypes::UNICAST); - if (!$agentJobId){ - continue; - } - - $this->createService->__invoke($client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); - } - break; - - - case DeployMethodTypes::MULTICAST_UFTP: - case DeployMethodTypes::MULTICAST_UFTP_DIRECT: - case DeployMethodTypes::MULTICAST_UDPCAST: - case DeployMethodTypes::MULTICAST_UDPCAST_DIRECT: - foreach ($input->clients as $client) { - $inputData = [ - 'method' => $input->method, - 'client' => $client->getEntity()->getUuid(), - 'image' => $image->getUuid(), - 'mcastIp' => $input->mcastIp, - 'mcastPort' => $input->mcastPort, - 'mcastSpeed' => $input->mcastSpeed, - 'mcastMode' => $input->mcastMode, - 'numDisk' => (string) $input->diskNumber, - 'numPartition' => (string) $input->partitionNumber, - ]; - - try { - $this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity()); - } catch (\Exception $e) { - continue; - } - - $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client->getEntity(), DeployMethodTypes::MULTICAST); - if (!$agentJobId){ - continue; - } - - $this->createService->__invoke($client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); - } - break; - - case DeployMethodTypes::TORRENT: - foreach ($input->clients as $client) { - $inputData = [ - 'method' => $input->method, - 'client' => $client->getEntity()->getUuid(), - 'image' => $image->getUuid(), - 'p2pMode' => $input->p2pMode, - 'p2pTime' => $input->p2pTime, - 'numDisk' => (string) $input->diskNumber, - 'numPartition' => (string) $input->partitionNumber, - ]; - - try { - $this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity(), $this->httpClient); - } catch (\Exception $e) { - continue; - } - - $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client->getEntity(), DeployMethodTypes::TORRENT); - if (!$agentJobId){ - continue; - } - - $this->createService->__invoke($client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); - } - - break; + if ($input->type === 'monolithic') { + $this->handleMonolithicDeployment($input, $image); } return new JsonResponse(data: [], status: Response::HTTP_OK); } + + private function handleMonolithicDeployment(DeployImageInput $input, ImageImageRepository $image): void + { + switch ($input->method) { + case DeployMethodTypes::UNICAST: + case DeployMethodTypes::UNICAST_DIRECT: + $this->handleUnicastDeployment($input, $image); + break; + case DeployMethodTypes::MULTICAST_UFTP: + case DeployMethodTypes::MULTICAST_UFTP_DIRECT: + case DeployMethodTypes::MULTICAST_UDPCAST: + case DeployMethodTypes::MULTICAST_UDPCAST_DIRECT: + $this->handleMulticastDeployment($input, $image); + break; + case DeployMethodTypes::TORRENT: + $this->handleTorrentDeployment($input, $image); + break; + } + } + + private function handleUnicastDeployment(DeployImageInput $input, ImageImageRepository $image): void + { + foreach ($input->clients as $client) { + $inputData = $this->createInputData($input, $image, $client->getEntity()); + $this->processDeployment($client->getEntity(), $input, $image, $inputData, DeployMethodTypes::UNICAST); + } + } + + private function handleMulticastDeployment(DeployImageInput $input, ImageImageRepository $image): void + { + foreach ($input->clients as $client) { + $inputData = $this->createMulticastInputData($input, $image, $client->getEntity()); + + try { + $this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity()); + } catch (\Exception $e) { + continue; + } + + $this->processDeployment($client->getEntity(), $input, $image, $inputData, DeployMethodTypes::MULTICAST); + } + } + + private function handleTorrentDeployment(DeployImageInput $input, ImageImageRepository $image): void + { + foreach ($input->clients as $client) { + $inputData = $this->createTorrentInputData($input, $image, $client->getEntity()); + + try { + $response = $this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity(), $this->httpClient); + + if (is_array($response) && isset($response['code']) && $response['code'] === 500) { + throw new \Exception('Error del servidor OgRepository: ' . ($response['error'] ?? 'Error desconocido') . ' - ' . ($response['details'] ?? '')); + } + + } catch (\Exception $e) { + throw $e; + } + + $this->processDeployment($client->getEntity(), $input, $image, $inputData, DeployMethodTypes::TORRENT); + } + } + + private function processDeployment($client, DeployImageInput $input, ImageImageRepository $image, array $inputData, string $deployType): void + { + $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client, $deployType); + + if (!$agentJobId) { + if ($input->queue) { + $this->createService->__invoke($client, CommandTypes::DEPLOY_IMAGE, TraceStatus::PENDING, null, $inputData); + } + return; + } + + $this->createService->__invoke($client, CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); + } + + private function createInputData(DeployImageInput $input, ImageImageRepository $image, $client): array + { + return [ + 'method' => $input->method, + 'client' => $client->getUuid(), + 'image' => $image->getUuid(), + 'imageName' => $image->getName(), + 'numDisk' => (string) $input->diskNumber, + 'numPartition' => (string) $input->partitionNumber, + 'type' => $input->type, + ]; + } + + private function createMulticastInputData(DeployImageInput $input, ImageImageRepository $image, $client): array + { + return array_merge($this->createInputData($input, $image, $client), [ + 'mcastIp' => $input->mcastIp, + 'mcastPort' => $input->mcastPort, + 'mcastSpeed' => $input->mcastSpeed, + 'mcastMode' => $input->mcastMode, + ]); + } + + private function createTorrentInputData(DeployImageInput $input, ImageImageRepository $image, $client): array + { + return array_merge($this->createInputData($input, $image, $client), [ + 'p2pMode' => $input->p2pMode, + 'p2pTime' => $input->p2pTime, + ]); + } } diff --git a/src/Controller/OgAgent/AbstractOgAgentController.php b/src/Controller/OgAgent/AbstractOgAgentController.php index 5739ef2..d89b684 100644 --- a/src/Controller/OgAgent/AbstractOgAgentController.php +++ b/src/Controller/OgAgent/AbstractOgAgentController.php @@ -4,6 +4,7 @@ namespace App\Controller\OgAgent; use App\Controller\OgRepository\Git\CreateRepositoryAction; use App\Controller\OgRepository\Git\CreateTagAction; +use App\Controller\OgRepository\Git\SshKeyAction; use App\Service\CreatePartitionService; use App\Service\Trace\CreateService; use Doctrine\ORM\EntityManagerInterface; @@ -28,7 +29,7 @@ abstract class AbstractOgAgentController extends AbstractController protected readonly LoggerInterface $logger, protected readonly CreateService $createService, protected readonly CreateRepositoryAction $createRepositoryAction, - protected readonly CreateTagAction $createTagAction, + protected readonly SshKeyAction $sshKeyAction, #[Autowire(env: 'SSL_ENABLED')] private readonly string $sslEnabled, ) diff --git a/src/Controller/OgAgent/CreateImageAction.php b/src/Controller/OgAgent/CreateImageAction.php index 6a3f032..32b96a6 100644 --- a/src/Controller/OgAgent/CreateImageAction.php +++ b/src/Controller/OgAgent/CreateImageAction.php @@ -5,10 +5,9 @@ declare(strict_types=1); namespace App\Controller\OgAgent; use App\Controller\OgRepository\Git\CreateRepositoryAction; -use App\Controller\OgRepository\Git\CreateTagAction; use App\Entity\Client; use App\Entity\Command; -use App\Entity\GitImageRepository; +use App\Entity\GitRepository; use App\Entity\Image; use App\Entity\ImageImageRepository; use App\Entity\ImageRepository; @@ -43,9 +42,11 @@ class CreateImageAction extends AbstractOgAgentController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(Image $image, ?Partition $partition = null, ?Client $client = null): JsonResponse + public function __invoke(bool $queue, Image $image, ?Partition $partition = null, ?Client $client = null, ?string $gitRepositoryName = null): JsonResponse { - if (!$image->getClient()->getIp()) { + $client = $client ?? $image->getClient(); + + if (!$client->getIp()) { throw new BadRequestHttpException('IP is required'); } @@ -76,25 +77,13 @@ class CreateImageAction extends AbstractOgAgentController $this->entityManager->persist($imageImageRepository); - return $this->createMonolithicImage($imageImageRepository, $partitionInfo, $image, $repository, $client); + return $this->createMonolithicImage($imageImageRepository, $partitionInfo, $image, $repository, $client, $queue); } else { $repository = $image->getClient()->getRepository(); - $latestImageRepo = $this->entityManager->getRepository(GitImageRepository::class)->findLatestVersionByImageAndRepository($image, $repository); - - $gitImageRepository = new GitImageRepository(); - $gitImageRepository->setName($image->getName().'_v'.($latestImageRepo ? $latestImageRepo->getVersion() + 1 : 1)); - $gitImageRepository->setImage($image); - $gitImageRepository->setRepository($repository); - $gitImageRepository->setStatus(ImageStatus::IN_PROGRESS); - $gitImageRepository->setVersion($latestImageRepo ? $latestImageRepo->getVersion() + 1 : 1); - $gitImageRepository->setTag('v'.($latestImageRepo ? $latestImageRepo->getVersion() + 1 : 1)); - $gitImageRepository->setCreated(false); - - $this->entityManager->persist($gitImageRepository); - $this->entityManager->persist($image); - $this->entityManager->flush(); - - return $this->createGitImage($gitImageRepository, $partitionInfo, $image, $repository); + + // 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); } } @@ -109,9 +98,19 @@ class CreateImageAction extends AbstractOgAgentController array $partitionInfo, Image $image, ImageRepository $repository, - ?Client $client = null + ?Client $client = null, + bool $queue = false ): JsonResponse { + if (!isset($partitionInfo['numDisk'], $partitionInfo['numPartition'], $partitionInfo['partitionCode'], $partitionInfo['filesystem'])) { + throw new BadRequestHttpException('Missing required partition information'); + } + + $client = $client ?? $image->getClient(); + if (!$client->getIp() || !$client->getToken()) { + throw new BadRequestHttpException('Client IP or token is missing'); + } + $data = [ 'dsk' => (string) $partitionInfo['numDisk'], 'par' => (string) $partitionInfo['numPartition'], @@ -134,85 +133,219 @@ class CreateImageAction extends AbstractOgAgentController $data['cpt'] = dechex($partitionTypeCode); } else { - throw new Exception("El tipo de partición '$partitionCode' no se encontró en la lista."); + throw new BadRequestHttpException("Invalid partition code: {$partitionCode}"); } - $client = $client ?? $image->getClient(); $this->logger->info('Creating image', ['image' => $image->getId()]); - $response = $this->createRequest( - method: 'POST', - url: 'https://'.$client->getIp().':8000/opengnsys/CrearImagen', - params: [ - 'json' => $data, - ], - token: $client->getToken(), - ); + try { + $response = $this->createRequest( + method: 'POST', + url: 'https://'.$client->getIp().':8000/opengnsys/CrearImagen', + params: [ + 'json' => $data, + ], + token: $client->getToken(), + ); - $this->logger->info('Creating image', ['image' => $imageImageRepository->getName(), 'repository' => $repository->getIp()]); + $this->logger->info('Creating image', ['image' => $imageImageRepository->getName(), 'repository' => $repository->getIp()]); - if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { - throw new BadRequestHttpException('Error creating image'); + if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + if ($queue) { + $inputData = [ + 'method' => 'CrearImagen', + 'type' => 'monolithic', + 'client' => $client->getUuid(), + 'image' => $image->getUuid(), + 'partitionCode' => $partitionInfo['partitionCode'], + 'partitionType' => $partitionInfo['filesystem'], + 'repository' => $repository->getIp(), + 'name' => $image->getName().'_v'.$imageImageRepository->getVersion(), + ]; + $this->createService->__invoke($client, CommandTypes::CREATE_IMAGE, TraceStatus::PENDING, null, $inputData); + return new JsonResponse(data: [], status: Response::HTTP_OK); + } + + throw new BadRequestHttpException('Error creating image: ' . ($response['message'] ?? 'Unknown error')); + } + + if (!isset($response['job_id'])) { + throw new BadRequestHttpException('No job ID received from server'); + } + + $jobId = $response['job_id']; + + try { + $client->setStatus(ClientStatus::BUSY); + $imageImageRepository->setStatus(ImageStatus::IN_PROGRESS); + + $this->entityManager->persist($client); + $this->entityManager->persist($imageImageRepository); + $this->entityManager->flush(); + + $inputData = [ + 'method' => 'CrearImagen', + 'type' => 'monolithic', + 'client' => $client->getUuid(), + 'image' => $image->getUuid(), + 'partitionCode' => $partitionInfo['partitionCode'], + 'partitionType' => $partitionInfo['filesystem'], + 'repository' => $repository->getIp(), + 'name' => $image->getName().'_v'.$imageImageRepository->getVersion(), + ]; + + $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) { + $client->setStatus(ClientStatus::OG_LIVE); + $this->entityManager->persist($client); + $this->entityManager->flush(); + throw $e; + } + } catch (Exception $e) { + $this->logger->error('Error in monolithic image creation process', [ + 'repository' => $repository->getId(), + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return new JsonResponse( + data: ['error' => $e->getMessage()], + status: Response::HTTP_INTERNAL_SERVER_ERROR + ); } - - $jobId = $response['job_id']; - - $client->setStatus(ClientStatus::BUSY); - $this->entityManager->persist($client); - $this->entityManager->flush(); - - $inputData = [ - 'method' => 'CrearImagen', - 'type' => 'monolithic', - 'client' => $client->getUuid(), - 'image' => $image->getUuid(), - 'partitionCode' => $partitionInfo['partitionCode'], - 'partitionType' => $partitionInfo['filesystem'], - 'repository' => $repository->getIp(), - 'name' => $image->getName().'_v'.$imageImageRepository->getVersion(), - ]; - - $this->createService->__invoke($image->getClient(), CommandTypes::CREATE_IMAGE, TraceStatus::IN_PROGRESS, $jobId, $inputData); - - return new JsonResponse(data: $image, status: Response::HTTP_OK); } public function createGitImage( - GitImageRepository $gitImageRepository, - array $partitionInfo, Image $image, - ImageRepository $repository + array $partitionInfo, + ImageRepository $repository, + bool $queue = false, + ?string $gitRepositoryName = null ): JsonResponse { - if (!isset($partitonInfo)) { - try { - $this->createRepositoryAction->__invoke($image, $repository); - } catch (Exception $e) { - $this->logger->error('Error creating repository', ['repository' => $repository->getId(), 'error' => $e->getMessage()]); - - return new JsonResponse( - data: ['error' => $e->getMessage()], - status: Response::HTTP_INTERNAL_SERVER_ERROR - ); - } - } else { - try { - $this->createTagAction->__invoke($image, $repository, $gitImageRepository); - } catch (Exception $e) { - $this->logger->error('Error creating tag', ['repository' => $repository->getId(), 'error' => $e->getMessage()]); - - return new JsonResponse( - data: ['error' => $e->getMessage()], - status: Response::HTTP_INTERNAL_SERVER_ERROR - ); - } + if (!isset($partitionInfo['numDisk'], $partitionInfo['numPartition'], $partitionInfo['partitionCode'], $partitionInfo['filesystem'])) { + throw new BadRequestHttpException('Missing required partition information'); } - return new JsonResponse( - data: ['message' => 'Repository created successfully'], - status: Response::HTTP_OK - ); + $client = $image->getClient(); + if (!$client->getIp() || !$client->getToken()) { + throw new BadRequestHttpException('Client IP or token is missing'); + } - //TODO: llamar al endpoint del agente. + try { + $data = [ + 'dsk' => (string) $partitionInfo['numDisk'], + 'par' => (string) $partitionInfo['numPartition'], + 'cpt' => null, + 'idi' => $gitRepositoryName ?: $image->getUuid(), + 'nci' => $gitRepositoryName ?: $image->getName(), + 'ipr' => $repository->getIp(), + 'nfn' => 'CrearImagenGit', + 'tag' => '', + 'ids' => '0' + ]; + + $partitionTypes = PartitionTypes::getPartitionTypes(); + $partitionCode = $partitionInfo['partitionCode']; + $cptKey = array_search($partitionCode, array_column($partitionTypes, 'name'), true); + + if ($cptKey !== false) { + $keys = array_keys($partitionTypes); + $partitionTypeCode = $keys[$cptKey]; + $data['cpt'] = dechex($partitionTypeCode); + } else { + throw new BadRequestHttpException("Invalid partition code: {$partitionCode}"); + } + + //$this->sshKeyAction->__invoke($repository, $client); + + $response = $this->createRequest( + method: 'POST', + url: 'https://'.$client->getIp().':8000/opengnsys/CrearImagenGit', + params: [ + 'json' => $data, + ], + token: $client->getToken(), + ); + + if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + if ($queue) { + $inputData = [ + 'method' => 'CrearImagenGit', + 'type' => 'git', + 'client' => $client->getUuid(), + 'image' => $image->getUuid(), + 'partitionCode' => $partitionInfo['partitionCode'], + 'partitionType' => $partitionInfo['filesystem'], + 'repository' => $repository->getIp(), + 'name' => $image->getName(), + ]; + + $this->createService->__invoke($client, CommandTypes::CREATE_IMAGE_GIT, TraceStatus::PENDING, null, $inputData); + return new JsonResponse(data: [], status: Response::HTTP_OK); + } + + throw new BadRequestHttpException('Error creating image: ' . ($response['message'] ?? 'Unknown error')); + } + + if (!isset($response['job_id'])) { + throw new BadRequestHttpException('No job ID received from server'); + } + + $jobId = $response['job_id']; + + try { + $client->setStatus(ClientStatus::BUSY); + + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $inputData = [ + 'method' => 'CrearImagenGit', + 'type' => 'git', + 'client' => $client->getUuid(), + 'image' => $image->getUuid(), + 'partitionCode' => $partitionInfo['partitionCode'], + 'partitionType' => $partitionInfo['filesystem'], + 'repository' => $repository->getIp(), + 'name' => $image->getName(), + ]; + + $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) { + $client->setStatus(ClientStatus::OG_LIVE); + $this->entityManager->persist($client); + $this->entityManager->flush(); + throw $e; + } + + } catch (Exception $e) { + $this->logger->error('Error in git image creation process', [ + 'repository' => $repository->getId(), + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return new JsonResponse( + data: ['error' => $e->getMessage()], + status: Response::HTTP_INTERNAL_SERVER_ERROR + ); + } } } diff --git a/src/Controller/OgAgent/DeployImageAction.php b/src/Controller/OgAgent/DeployImageAction.php index 6bebf9e..dbc4085 100644 --- a/src/Controller/OgAgent/DeployImageAction.php +++ b/src/Controller/OgAgent/DeployImageAction.php @@ -38,7 +38,7 @@ class DeployImageAction extends AbstractOgAgentController * @throws TransportExceptionInterface * @throws ServerExceptionInterface */ - public function __invoke(ImageImageRepository $imageImageRepository, DeployImageInput $input, Client $client, string $method) + public function __invoke(ImageImageRepository $imageImageRepository, DeployImageInput $input, Client $client, ?string $method = null) { $image = $imageImageRepository->getImage(); @@ -46,6 +46,10 @@ class DeployImageAction extends AbstractOgAgentController throw new BadRequestHttpException('IP is required'); } + if ($input->type === 'git') { + throw new BadRequestHttpException('Use DeployGitImageAction for Git images'); + } + $method = match ($input->method) { DeployMethodTypes::MULTICAST_UFTP_DIRECT, DeployMethodTypes::MULTICAST_UDPCAST_DIRECT, => 'multicast-direct', DeployMethodTypes::MULTICAST, DeployMethodTypes::MULTICAST_UFTP, DeployMethodTypes::MULTICAST_UDPCAST => 'multicast', @@ -83,16 +87,22 @@ class DeployImageAction extends AbstractOgAgentController 'ids' => '0' ]; + $url = 'https://'.$client->getIp().':8000/opengnsys/RestaurarImagen'; + $response = $this->createRequest( method: 'POST', - url: 'https://'.$client->getIp().':8000/opengnsys/RestaurarImagen', + url: $url, params: [ 'json' => $data, ], token: $client->getToken(), ); - $this->logger->info('Deploying image', [ 'image' => $imageImageRepository->getName(), 'repository' => $repository->getIp()]); + $this->logger->info('Deploying image', [ + 'image' => $imageImageRepository->getName(), + 'repository' => $repository->getIp(), + 'client' => $client->getIp() + ]); if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { throw new BadRequestHttpException('Error deploying image'); diff --git a/src/Controller/OgAgent/Webhook/AgentSessionController.php b/src/Controller/OgAgent/Webhook/AgentSessionController.php index 6723ea8..8c3e72a 100644 --- a/src/Controller/OgAgent/Webhook/AgentSessionController.php +++ b/src/Controller/OgAgent/Webhook/AgentSessionController.php @@ -166,14 +166,16 @@ class AgentSessionController extends AbstractController return new JsonResponse(['message' => 'Client not found'], Response::HTTP_NOT_FOUND); } - switch ($data['ostype']) { - case 'Linux': + $status = $client->getStatus(); + + switch ($status) { + case ClientStatus::LINUX_SESSION: $client->setStatus(ClientStatus::LINUX); break; - case 'Windows': + case ClientStatus::WINDOWS_SESSION: $client->setStatus(ClientStatus::WINDOWS); break; - case 'MacOS': + case ClientStatus::MACOS_SESSION: $client->setStatus(ClientStatus::MACOS); break; @@ -187,7 +189,7 @@ class AgentSessionController extends AbstractController $this->logger->info('Client logged out', [ 'ip' => $data['ip'], 'user' => $data['user'], - 'ostype' => $data['ostype'], + 'status' => $status, ]); return new JsonResponse([], Response::HTTP_OK); diff --git a/src/Controller/OgAgent/Webhook/StatusController.php b/src/Controller/OgAgent/Webhook/StatusController.php index b4df0f1..75a998c 100644 --- a/src/Controller/OgAgent/Webhook/StatusController.php +++ b/src/Controller/OgAgent/Webhook/StatusController.php @@ -1,5 +1,7 @@ logger->info('Webhook data received', $data); - // Esta parte del codigo nos indica si el cliente se encuentra activo if (isset($data['iph']) && isset($data['timestamp'])) { $client = $this->entityManager->getRepository(Client::class)->findOneBy(['ip' => $data['iph']]); if (!$client) { @@ -81,11 +84,37 @@ class StatusController extends AbstractController if (isset($data['progress'])){ $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); if ($trace){ - $trace->setProgress($data['progress'] * 100); + $trace->setProgress((int)($data['progress'] * 100)); $this->entityManager->persist($trace); $this->entityManager->flush(); } } + + if (isset($data['nfn']) && $data['nfn'] === self::CREATE_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 creation 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']]); @@ -142,7 +171,7 @@ class StatusController extends AbstractController $this->logger->info('Image updated. Success.', ['image' => (string) $imageImageRepository->getUuid()]); } - if (isset($data['nfn']) && ($data['nfn'] === self::RESTORE_IMAGE || $data['nfn'] === self::CONFIGURE_IMAGE)) { + if (isset($data['nfn']) && ($data['nfn'] === self::RESTORE_IMAGE || $data['nfn'] === self::CONFIGURE_IMAGE || $data['nfn'] === self::RESTORE_IMAGE_GIT)) { $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); $client = $trace->getClient(); diff --git a/src/Controller/OgRepository/AbstractOgRepositoryController.php b/src/Controller/OgRepository/AbstractOgRepositoryController.php index eb61f33..80824e2 100644 --- a/src/Controller/OgRepository/AbstractOgRepositoryController.php +++ b/src/Controller/OgRepository/AbstractOgRepositoryController.php @@ -45,7 +45,7 @@ abstract class AbstractOgRepositoryController extends AbstractController 'accept' => 'application/json', 'Content-Type' => 'application/json' ], - 'timeout' => 10, + 'timeout' => 30, ]); try { @@ -56,7 +56,7 @@ abstract class AbstractOgRepositoryController extends AbstractController return [ 'code' => Response::HTTP_INTERNAL_SERVER_ERROR, - 'error' => 'Client/Server error', + 'error' => $e->getMessage(), 'details' => $e->getMessage(), ]; } catch (TransportExceptionInterface $e) { @@ -64,7 +64,7 @@ abstract class AbstractOgRepositoryController extends AbstractController return [ 'code' => Response::HTTP_INTERNAL_SERVER_ERROR, - 'error' => 'Transport error', + 'error' => $e->getMessage(), 'details' => $e->getMessage(), ]; } diff --git a/src/Dto/Input/DeployImageInput.php b/src/Dto/Input/DeployImageInput.php index 8f194cc..bb4fc9a 100644 --- a/src/Dto/Input/DeployImageInput.php +++ b/src/Dto/Input/DeployImageInput.php @@ -4,8 +4,6 @@ namespace App\Dto\Input; use ApiPlatform\Metadata\ApiProperty; use App\Dto\Output\ClientOutput; -use App\Dto\Output\ImageOutput; -use App\Dto\Output\PartitionOutput; use App\Validator\Constraints\ClientsHaveSamePartitionCount; use App\Validator\Constraints\OrganizationalUnitMulticastMode; use App\Validator\Constraints\OrganizationalUnitMulticastPort; @@ -17,49 +15,63 @@ class DeployImageInput { #[Groups(['image-image-repository:write'])] #[ApiProperty(description: 'The type of the image deployment', example: "")] - public ?string $type = null; + public ?string $type = 'monolithic'; #[Groups(['image-image-repository:write'])] - #[ApiProperty(description: 'The type of the image deployment', example: "")] + #[ApiProperty(description: 'The deployment method', example: "unicast")] public ?string $method = null; /** * @var ClientOutput[] */ #[Groups(['image-image-repository:write'])] - #[ApiProperty(description: 'The client to deploy the image')] + #[ApiProperty(description: 'The clients to deploy the image to')] public ?array $clients = []; #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'The disk number to deploy to', example: 1)] public ?int $diskNumber = null; #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'The partition number to deploy to', example: 1)] public ?int $partitionNumber = null; #[OrganizationalUnitP2PMode] #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'P2P mode for torrent deployment', example: "seed")] public ?string $p2pMode = null; #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'P2P time for torrent deployment', example: 3600)] public ?int $p2pTime = null; #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'Multicast IP address', example: "239.255.255.250")] public ?string $mcastIp = null; #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'Multicast speed in Mbps', example: 100)] public ?int $mcastSpeed = null; #[OrganizationalUnitMulticastPort] #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'Multicast port', example: 8000)] public ?int $mcastPort = null; #[OrganizationalUnitMulticastMode] #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'Multicast mode', example: "full")] public ?string $mcastMode = null; #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'Maximum number of clients for multicast', example: 50)] public ?int $maxClients = null; #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'Maximum time for deployment in seconds', example: 3600)] public ?int $maxTime = null; -} \ No newline at end of file + + #[Groups(['image-image-repository:write'])] + #[ApiProperty(description: 'Whether to queue the deployment', example: false)] + public bool $queue = false; +} diff --git a/src/Dto/Input/ImageInput.php b/src/Dto/Input/ImageInput.php index f2dd9de..f472a78 100644 --- a/src/Dto/Input/ImageInput.php +++ b/src/Dto/Input/ImageInput.php @@ -21,7 +21,6 @@ use Symfony\Component\Validator\Constraints as Assert; final class ImageInput { - #[Assert\NotBlank(message: 'validators.image.name.not_blank')] #[Groups(['image:write'])] #[ApiProperty(description: 'The name of the image', example: "Image 1")] public ?string $name = null; @@ -73,6 +72,14 @@ final class ImageInput #[ApiProperty(description: 'The global property of the image')] public ?bool $isGlobal = false; + #[Groups(['image:write'])] + #[ApiProperty(description: 'The queue property of the image')] + public bool $queue = false; + + #[Groups(['image:write'])] + #[ApiProperty(description: 'The name of the Git repository to use for this image', example: "mi-repositorio")] + public ?string $gitRepository = null; + public function __construct(?Image $image = null) { if (!$image) { diff --git a/src/Model/DeployMethodTypes.php b/src/Model/DeployMethodTypes.php index 4c48e55..ef22ab3 100644 --- a/src/Model/DeployMethodTypes.php +++ b/src/Model/DeployMethodTypes.php @@ -12,11 +12,13 @@ final class DeployMethodTypes public const string UNICAST = 'unicast'; public const string UNICAST_DIRECT = 'unicast-direct'; public const string TORRENT = 'p2p'; + public const string GIT = 'git'; private const array DEPLOYMENT_METHOD_TYPES = [ self::MULTICAST => 'Multicast', self::UNICAST => 'Unicast', self::TORRENT => 'Torrent', + self::GIT => 'Git', ]; public static function getDeploymentMethodTypes(): array From e325578e2b6b9005a78f434ad134c9588fcded1b Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 26 Jun 2025 17:06:48 +0200 Subject: [PATCH 3/6] refs #1975. Integration ogGit. Crete image and deploy --- src/Controller/OgAgent/AbstractOgAgentController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Controller/OgAgent/AbstractOgAgentController.php b/src/Controller/OgAgent/AbstractOgAgentController.php index d89b684..e472cab 100644 --- a/src/Controller/OgAgent/AbstractOgAgentController.php +++ b/src/Controller/OgAgent/AbstractOgAgentController.php @@ -3,7 +3,6 @@ namespace App\Controller\OgAgent; use App\Controller\OgRepository\Git\CreateRepositoryAction; -use App\Controller\OgRepository\Git\CreateTagAction; use App\Controller\OgRepository\Git\SshKeyAction; use App\Service\CreatePartitionService; use App\Service\Trace\CreateService; From 7b74c9ab70510711fbbd8f02fe77b3c1e9e4ffae Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 26 Jun 2025 17:08:40 +0200 Subject: [PATCH 4/6] refs #2327. KillJob integration --- config/api_platform/Trace.yaml | 18 ++++++ src/Controller/OgAgent/KillJobAction.php | 71 ++++++++++++++++++++++++ src/Dto/Input/KillJobInput.php | 15 +++++ src/EventListener/ClientMacListener.php | 6 +- src/Model/CommandTypes.php | 4 ++ 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/Controller/OgAgent/KillJobAction.php create mode 100644 src/Dto/Input/KillJobInput.php diff --git a/config/api_platform/Trace.yaml b/config/api_platform/Trace.yaml index 3d64fa4..86ce3a6 100644 --- a/config/api_platform/Trace.yaml +++ b/config/api_platform/Trace.yaml @@ -23,6 +23,24 @@ resources: uriTemplate: /traces/server/{uuid}/cancel controller: App\Controller\OgRepository\Image\CancelTransmissionAction + cancel_multiple_traces: + shortName: Trace Server + description: Cancel Multiple Traces in OgRepository + controller: App\Controller\CancelMultipleTracesAction + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\CancelMultipleTracesInput + uriTemplate: /traces/cancel-multiple + + kill_job: + shortName: Kill Job + description: Kill Job in OgAgent + controller: App\Controller\OgAgent\KillJobAction + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\KillJobInput + uriTemplate: /traces/{uuid}/kill-job + order: createdAt: DESC diff --git a/src/Controller/OgAgent/KillJobAction.php b/src/Controller/OgAgent/KillJobAction.php new file mode 100644 index 0000000..2877cce --- /dev/null +++ b/src/Controller/OgAgent/KillJobAction.php @@ -0,0 +1,71 @@ +getClient(); + + if (!$client->getIp()) { + throw new BadRequestHttpException('IP is required'); + } + + $data = [ + 'job_id' => $input->jobId + ]; + + $response = $this->createRequest( + method: 'POST', + url: 'https://'.$client->getIp().':8000/opengnsys/KillJob', + params: [ + 'json' => $data, + ], + token: $client->getToken(), + ); + + if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + throw new BadRequestHttpException('Error killing job: '.$response['error']); + } + + $this->logger->info('Killing job', ['client' => $client->getId(), 'job_id' => $input->jobId]); + + $trace->setStatus(TraceStatus::CANCELLED); + $this->entityManager->persist($trace); + $this->entityManager->flush(); + + $inputData = [ + 'job_id' => $input->jobId, + 'client' => $client->getId(), + 'trace' => $trace->getUuid(), + 'command' => $trace->getCommand(), + ]; + + $this->createService->__invoke($client, CommandTypes::KILL_JOB, TraceStatus::CANCELLED, $input->jobId, $inputData); + + return new JsonResponse(data: $trace, status: Response::HTTP_OK); + } +} diff --git a/src/Dto/Input/KillJobInput.php b/src/Dto/Input/KillJobInput.php new file mode 100644 index 0000000..4ac88f3 --- /dev/null +++ b/src/Dto/Input/KillJobInput.php @@ -0,0 +1,15 @@ +putHostAction->__invoke($oldMac, $client); - $this->deleteAction->__invoke($oldMac); + if ($client->getSubnet()) { + $this->putHostAction->__invoke($oldMac, $client); + $this->deleteAction->__invoke($oldMac); + } } } \ No newline at end of file diff --git a/src/Model/CommandTypes.php b/src/Model/CommandTypes.php index 63c48eb..f309dcc 100644 --- a/src/Model/CommandTypes.php +++ b/src/Model/CommandTypes.php @@ -7,6 +7,7 @@ final class CommandTypes public const string DEPLOY_IMAGE = 'deploy-image'; 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 CONVERT_IMAGE = 'convert-image'; public const string CREATE_IMAGE_AUX_FILE = 'create-image-aux-file'; public const string BACKUP_IMAGE = 'backup-image'; @@ -26,11 +27,13 @@ final class CommandTypes public const string REMOVE_CACHE_IMAGE = 'remove-cache-image'; public const string HARDWARE_INVENTORY = 'hardware-inventory'; public const string SOFTWARE_INVENTORY = 'software-inventory'; + public const string KILL_JOB = 'kill-job'; private const array COMMAND_TYPES = [ self::DEPLOY_IMAGE => 'Deploy Image', self::RESTORE_IMAGE => 'Update Cache', self::CREATE_IMAGE => 'Create Image', + self::CREATE_IMAGE_GIT => 'Create 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', @@ -50,6 +53,7 @@ final class CommandTypes self::REMOVE_CACHE_IMAGE => 'Remove Cache Image', self::HARDWARE_INVENTORY => 'Hardware Inventory', self::SOFTWARE_INVENTORY => 'Software Inventory', + self::KILL_JOB => 'Kill Job', ]; public static function getCommandTypes(): array From 21cd73bee2cb78ef7f729a69580b51ec707dc965 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 26 Jun 2025 17:10:44 +0200 Subject: [PATCH 5/6] refs #2252. Queue actions. Empty queue and logging actions --- config/packages/monolog.yaml | 7 +-- src/Command/CheckClientAvailability.php | 2 +- src/Command/ExecutePendingTracesCommand.php | 49 +++++++++++++++++++ src/Controller/CancelMultipleTracesAction.php | 35 +++++++++++++ src/Controller/OgAgent/LoginAction.php | 6 ++- .../OgAgent/PartitionAssistantAction.php | 7 ++- src/Controller/OgAgent/PowerOffAction.php | 15 ++++-- src/Controller/OgAgent/RebootAction.php | 11 +++-- .../OgAgent/RemoveCacheImageAction.php | 13 +++-- src/Controller/OgAgent/RunScriptAction.php | 12 ++++- .../OgRepository/Image/DeployImageAction.php | 6 +++ .../OgRepository/Image/TransferAction.php | 2 + .../Webhook/ResponseController.php | 4 +- src/Dto/Input/CancelMultipleTracesInput.php | 17 +++++++ src/Dto/Input/CommandExecuteInput.php | 3 ++ src/Dto/Input/MultipleClientsInput.php | 3 ++ src/Dto/Input/PartitionPostInput.php | 3 ++ 17 files changed, 170 insertions(+), 25 deletions(-) create mode 100644 src/Command/ExecutePendingTracesCommand.php create mode 100644 src/Controller/CancelMultipleTracesAction.php create mode 100644 src/Dto/Input/CancelMultipleTracesInput.php diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml index 59cb0fb..c5b6aba 100644 --- a/config/packages/monolog.yaml +++ b/config/packages/monolog.yaml @@ -44,19 +44,16 @@ when@prod: level: error formatter: App\Formatter\CustomLineFormatter channels: ["!event"] - console: - type: console - process_psr_3_messages: false - channels: ["!event", "!doctrine"] syslog: type: syslog ident: "ogcore" - level: info + level: error formatter: App\Formatter\CustomLineFormatter channels: ["!event"] deprecation: type: stream channels: [deprecation] path: "%kernel.logs_dir%/%kernel.environment%.log" + level: error formatter: monolog.formatter.json diff --git a/src/Command/CheckClientAvailability.php b/src/Command/CheckClientAvailability.php index fb63a9f..da74d71 100644 --- a/src/Command/CheckClientAvailability.php +++ b/src/Command/CheckClientAvailability.php @@ -36,7 +36,7 @@ class CheckClientAvailability extends Command $threshold = (new \DateTime())->modify(' - '.self::THRESHOLD_MINUTES . ' minutes'); $startQueryTime = microtime(true); - $validStatuses = [ClientStatus::OG_LIVE, ClientStatus::WINDOWS, ClientStatus::LINUX, ClientStatus::MACOS]; + $validStatuses = [ClientStatus::OG_LIVE, ClientStatus::WINDOWS, ClientStatus::LINUX, ClientStatus::MACOS, ClientStatus::INITIALIZING, ClientStatus::LINUX_SESSION, ClientStatus::WINDOWS_SESSION]; $query = $this->entityManager->createQuery( 'UPDATE App\Entity\Client c diff --git a/src/Command/ExecutePendingTracesCommand.php b/src/Command/ExecutePendingTracesCommand.php new file mode 100644 index 0000000..71fbbd0 --- /dev/null +++ b/src/Command/ExecutePendingTracesCommand.php @@ -0,0 +1,49 @@ +entityManager->getRepository(Trace::class) + ->findBy(['status' => TraceStatus::PENDING]); + + $count = count($traces); + $io->info("Found $count pending traces"); + + foreach ($traces as $trace) { + $trace->setStatus(TraceStatus::IN_PROGRESS); + $this->entityManager->persist($trace); + } + + $this->entityManager->flush(); + + $executionTime = microtime(true) - $startTime; + $io->success("Updated $count traces to IN_PROGRESS status"); + $io->note("Execution time: " . round($executionTime, 3) . "s"); + + return Command::SUCCESS; + } +} \ No newline at end of file diff --git a/src/Controller/CancelMultipleTracesAction.php b/src/Controller/CancelMultipleTracesAction.php new file mode 100644 index 0000000..3fc55e1 --- /dev/null +++ b/src/Controller/CancelMultipleTracesAction.php @@ -0,0 +1,35 @@ +traces as $trace) { + /** @var Trace $trace */ + $trace = $trace->getEntity(); + + $trace->setStatus(TraceStatus::CANCELLED); + $this->entityManager->persist($trace); + } + + $this->entityManager->flush(); + + return new JsonResponse(data: 'Traces cancelled successfully', status: Response::HTTP_OK); + } +} \ No newline at end of file diff --git a/src/Controller/OgAgent/LoginAction.php b/src/Controller/OgAgent/LoginAction.php index 67d6058..7730767 100644 --- a/src/Controller/OgAgent/LoginAction.php +++ b/src/Controller/OgAgent/LoginAction.php @@ -53,7 +53,8 @@ class LoginAction extends AbstractOgAgentController } if ($client->getStatus() !== ClientStatus::OG_LIVE) { - throw new BadRequestHttpException('Client is not in OG_LIVE status'); + $this->createService->__invoke($client, CommandTypes::LOGIN, TraceStatus::PENDING, null, []); + continue; } $data = [ @@ -73,7 +74,8 @@ class LoginAction extends AbstractOgAgentController ); if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { - throw new BadRequestHttpException('Error logging in: '.$response['error']); + $this->createService->__invoke($client, CommandTypes::LOGIN, TraceStatus::PENDING, null, []); + continue; } $this->logger->info('Login client', ['client' => $client->getId()]); diff --git a/src/Controller/OgAgent/PartitionAssistantAction.php b/src/Controller/OgAgent/PartitionAssistantAction.php index a443de1..8d54721 100644 --- a/src/Controller/OgAgent/PartitionAssistantAction.php +++ b/src/Controller/OgAgent/PartitionAssistantAction.php @@ -111,7 +111,12 @@ class PartitionAssistantAction extends AbstractOgAgentController $this->logger->info('Partition assistant', ['client' => $client->getId()]); if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { - throw new BadRequestHttpException('Error occurred while partitioning'); + if ($input->queue) { + $this->createService->__invoke($client, CommandTypes::PARTITION_AND_FORMAT, TraceStatus::PENDING, null, $data); + continue; + } + + continue; } $jobId = $response['job_id']; diff --git a/src/Controller/OgAgent/PowerOffAction.php b/src/Controller/OgAgent/PowerOffAction.php index 19ac568..2be4269 100644 --- a/src/Controller/OgAgent/PowerOffAction.php +++ b/src/Controller/OgAgent/PowerOffAction.php @@ -47,12 +47,10 @@ class PowerOffAction extends AbstractOgAgentController throw new BadRequestHttpException('IP is required'); } - if ($client->getStatus() === ClientStatus::OFF) { + if ($client->getStatus() === ClientStatus::OFF || $client->getStatus() === ClientStatus::TURNING_OFF || $client->getStatus() === ClientStatus::DISCONNECTED) { continue; } - $endpoint = $client->getStatus() === ClientStatus::OG_LIVE ? 'opengnsys/Apagar' : 'opengnsys/poweroff'; - $data = [ 'nfn' => 'Apagar', 'ids' => '0' @@ -60,7 +58,7 @@ class PowerOffAction extends AbstractOgAgentController $response = $this->createRequest( method: 'POST', - url: 'https://'.$client->getIp().':8000/'.$endpoint, + url: 'https://'.$client->getIp().':8000/opengnsys/Apagar', params: [ 'json' => $data, ], @@ -68,7 +66,14 @@ class PowerOffAction extends AbstractOgAgentController ); if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { - throw new BadRequestHttpException('Error deploying image'); + $this->logger->error('Error powering off client', ['client' => $client->getId(), 'error' => $response['error']]); + + if ($input->queue) { + $this->createService->__invoke($client, CommandTypes::SHUTDOWN, TraceStatus::PENDING, null, []); + continue; + } + + continue; } $this->logger->info('Powering off client', ['client' => $client->getId()]); diff --git a/src/Controller/OgAgent/RebootAction.php b/src/Controller/OgAgent/RebootAction.php index 411ff7e..60f18d5 100644 --- a/src/Controller/OgAgent/RebootAction.php +++ b/src/Controller/OgAgent/RebootAction.php @@ -47,8 +47,6 @@ class RebootAction extends AbstractOgAgentController throw new BadRequestHttpException('IP is required'); } - $endpoint = $client->getStatus() === ClientStatus::OG_LIVE ? 'opengnsys/Reiniciar' : '/opengnsys/reboot'; - $data = [ 'nfn' => 'Reiniciar', 'ids' => '0' @@ -56,7 +54,7 @@ class RebootAction extends AbstractOgAgentController $response = $this->createRequest( method: 'POST', - url: 'https://'.$client->getIp().':8000/'.$endpoint, + url: 'https://'.$client->getIp().':8000/opengnsys/Reiniciar', params: [ 'json' => $data, ], @@ -64,7 +62,12 @@ class RebootAction extends AbstractOgAgentController ); if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { - throw new BadRequestHttpException('Error deploying image'); + if ($input->queue) { + $this->createService->__invoke($client, CommandTypes::REBOOT, TraceStatus::PENDING, null, []); + continue; + } + + continue; } $this->logger->info('Rebooting client', ['client' => $client->getId()]); diff --git a/src/Controller/OgAgent/RemoveCacheImageAction.php b/src/Controller/OgAgent/RemoveCacheImageAction.php index 8de9ff7..f7fa159 100644 --- a/src/Controller/OgAgent/RemoveCacheImageAction.php +++ b/src/Controller/OgAgent/RemoveCacheImageAction.php @@ -43,10 +43,11 @@ class RemoveCacheImageAction extends AbstractOgAgentController } if ($client->getStatus() !== ClientStatus::OG_LIVE) { - throw new BadRequestHttpException('Client is not in OG_LIVE status'); + $this->createService->__invoke($client, CommandTypes::REMOVE_CACHE_IMAGE, TraceStatus::PENDING, null, []); + continue; } - $script = `rm%20-r%20/opt/opengnsys/cache/opt/opengnsys/images/{$partition->getImage()->getName()}.*@'`; + $script = 'rm -r /opt/opengnsys/cache/opt/opengnsys/images/' . $partition->getImage()->getName() . '.*'; $data = [ 'nfn' => 'EjecutarScript', @@ -64,9 +65,13 @@ class RemoveCacheImageAction extends AbstractOgAgentController ); if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { - throw new BadRequestHttpException('Error logging in: '.$response['error']); + if ($input->queue) { + $this->createService->__invoke($client, CommandTypes::REMOVE_CACHE_IMAGE, TraceStatus::PENDING, null, []); + continue; + } + + continue; } - $this->logger->info('Login client', ['client' => $client->getId()]); $jobId = $response['job_id']; diff --git a/src/Controller/OgAgent/RunScriptAction.php b/src/Controller/OgAgent/RunScriptAction.php index eb9112a..16d95ee 100644 --- a/src/Controller/OgAgent/RunScriptAction.php +++ b/src/Controller/OgAgent/RunScriptAction.php @@ -57,7 +57,17 @@ class RunScriptAction extends AbstractOgAgentController ); if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { - throw new BadRequestHttpException('Error deploying image'); + $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; + } + + continue; } $this->logger->info('Powering off client', ['client' => $client->getId()]); diff --git a/src/Controller/OgRepository/Image/DeployImageAction.php b/src/Controller/OgRepository/Image/DeployImageAction.php index 8b8eb8c..2ca149d 100644 --- a/src/Controller/OgRepository/Image/DeployImageAction.php +++ b/src/Controller/OgRepository/Image/DeployImageAction.php @@ -35,6 +35,7 @@ class DeployImageAction extends AbstractOgRepositoryController $params = [ 'json' => [ 'ID_img' => $data->getImageFullsum(), + //'image_name' => $data->getName(), 'bitrate' => (string) $input->mcastSpeed.'M', 'ip' => $input->mcastIp, 'port' => $input->mcastPort, @@ -54,6 +55,11 @@ class DeployImageAction extends AbstractOgRepositoryController $content = $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/'.$type, $params); + // Verificar si la respuesta contiene un error HTTP 500 + if (isset($content['code']) && $content['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) { + throw new \Exception('Error del servidor OgRepository: ' . ($content['error'] ?? 'Error desconocido') . ' - ' . ($content['details'] ?? '')); + } + return new JsonResponse(data: [], status: Response::HTTP_OK); } } \ No newline at end of file diff --git a/src/Controller/OgRepository/Image/TransferAction.php b/src/Controller/OgRepository/Image/TransferAction.php index 61e3088..badbfa7 100644 --- a/src/Controller/OgRepository/Image/TransferAction.php +++ b/src/Controller/OgRepository/Image/TransferAction.php @@ -62,6 +62,8 @@ class TransferAction extends AbstractOgRepositoryController $inputData = [ 'imageName' => $image->getName(), + 'imageVersion' => $imageImageRepository->getVersion(), + 'imageCompleteName' => $imageImageRepository->getName(), 'imageUuid' => $image->getUuid(), 'imageImageRepositoryUuid' => $imageImageRepository->getUuid(), 'repositoryUuid' => $repository->getUuid(), diff --git a/src/Controller/OgRepository/Webhook/ResponseController.php b/src/Controller/OgRepository/Webhook/ResponseController.php index 05535dd..66198fd 100644 --- a/src/Controller/OgRepository/Webhook/ResponseController.php +++ b/src/Controller/OgRepository/Webhook/ResponseController.php @@ -92,9 +92,9 @@ class ResponseController extends AbstractOgRepositoryController $latestImageRepo = $this->entityManager->getRepository(ImageImageRepository::class)->findLatestVersionByImageAndRepository($image, $repository); $newImageRepo = new ImageImageRepository(); - $newImageRepo->setName($image->getName().'_v'.($latestImageRepo ? $latestImageRepo->getVersion() + 1 : 1)); + $newImageRepo->setName($originImageImageRepository->getName()); $newImageRepo->setImage($image); - $newImageRepo->setVersion($latestImageRepo ? $latestImageRepo->getVersion() + 1 : 1); + $newImageRepo->setVersion($originImageImageRepository->getVersion()); $newImageRepo->setRepository($repository); $newImageRepo->setStatus(ImageStatus::SUCCESS); diff --git a/src/Dto/Input/CancelMultipleTracesInput.php b/src/Dto/Input/CancelMultipleTracesInput.php new file mode 100644 index 0000000..825d804 --- /dev/null +++ b/src/Dto/Input/CancelMultipleTracesInput.php @@ -0,0 +1,17 @@ + Date: Thu, 26 Jun 2025 17:19:17 +0200 Subject: [PATCH 6/6] Added changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3646705..4eeff1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,15 @@ # Changelog +## [0.15.0] - 2025-06-26 +### Added +- Se ha añadido la integracion con ogGit para la gestion de imagenes. +- Se ha añadido la primera parte del sistema de cola de acciones. +- Integracion con el endpoint del agente KillJob +- Posibilidad de poder vaciar la cola de acciones. + +### Fixed +- Se ha corregido un bug que hacia que al mover clientes entre aulas, diera error en caso de que el cliente no tuviera PXE asignado. + +--- ## [0.14.2] - 2025-06-09 ### Improved - Cambio en el template base del menu. Ahora apunta a los script de python de manera correcta.