refs #614. Itnegration ogDhcp

feature/integration-dhcp
Manuel Aranda Rosales 2024-08-14 15:01:15 +02:00
parent 3f5004dddd
commit b26b215ffc
17 changed files with 556 additions and 1 deletions

2
.env
View File

@ -42,3 +42,5 @@ JWT_PASSPHRASE=8b9154df37ffa91ef9186ce095324e39e50ff3b023bb1ed34383abd019ba4515
###< lexik/jwt-authentication-bundle ###
OGBOOT_API_URL=http://localhost:8085
OGDHCP_API_URL=http://localhost:8085

View File

@ -9,7 +9,7 @@ resources:
groups: ['pxe-boot-file:write']
operations:
ApiPlatform\Metadata\GetCollection:
provider: App\State\Provider\PPxeBootFileProvider
provider: App\State\Provider\PxeBootFileProvider
filters:
- 'api_platform.filter.pxe_boot_file.order'
- 'api_platform.filter.pxe_boot_file.search'

View File

@ -0,0 +1,31 @@
resources:
App\Entity\Subnet:
processor: App\State\Processor\SubnetProcessor
input: App\Dto\Input\SubnetInput
output: App\Dto\Output\SubnetOutput
normalizationContext:
groups: ['default', 'subnet:read']
denormalizationContext:
groups: ['subnet:write']
operations:
ApiPlatform\Metadata\GetCollection:
provider: App\State\Provider\SubnetProvider
filters:
- 'api_platform.filter.subnet.order'
- 'api_platform.filter.subnet.search'
ApiPlatform\Metadata\Get:
provider: App\State\Provider\SubnetProvider
ApiPlatform\Metadata\Put:
provider: App\State\Provider\SubnetProvider
ApiPlatform\Metadata\Patch:
provider: App\State\Provider\SubnetProvider
ApiPlatform\Metadata\Post: ~
ApiPlatform\Metadata\Delete: ~
properties:
App\Entity\Subnet:
id:
identifier: false
uuid:
identifier: true

View File

@ -10,6 +10,7 @@ services:
bind:
$ogBootApiUrl: '%env(OGBOOT_API_URL)%'
$ogDhcpApiUrl: '%env(OGDHCP_API_URL)%'
App\:
resource: '../src/'
@ -118,3 +119,8 @@ services:
bind:
$collectionProvider: '@api_platform.doctrine.orm.state.collection_provider'
$itemProvider: '@api_platform.doctrine.orm.state.item_provider'
App\State\Provider\SubnetProvider:
bind:
$collectionProvider: '@api_platform.doctrine.orm.state.collection_provider'
$itemProvider: '@api_platform.doctrine.orm.state.item_provider'

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Controller\OgDhcp;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpKernel\Attribute\AsController;
#[AsController]
abstract class AbstractOgDhcpController extends AbstractController
{
public function __construct(
protected readonly string $ogDhcpApiUrl,
protected readonly EntityManagerInterface $entityManager
)
{
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Controller\OgDhcp\Subnet;
use App\Controller\OgDhcp\AbstractOgDhcpController;
use Symfony\Component\HttpKernel\Attribute\AsController;
#[AsController]
class DeleteAction extends AbstractOgDhcpController
{
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Controller\OgDhcp\Subnet;
use App\Controller\OgBoot\AbstractOgLiveController;
use App\Controller\OgDhcp\AbstractOgDhcpController;
use App\Entity\OgLive;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[AsController]
class GetAction extends AbstractOgDhcpController
{
/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function __invoke(OgLive $data, HttpClientInterface $httpClient): JsonResponse
{
try {
$response = $httpClient->request('GET', $this->ogDhcpApiUrl.'/opengnsys3/rest/dhcp/subnets/'.$data->getChecksum(), [
'headers' => [
'accept' => 'application/json',
],
]);
} catch (TransportExceptionInterface $e) {
return new JsonResponse( data: 'An error occurred', status: Response::HTTP_INTERNAL_SERVER_ERROR);
}
$data = json_decode($response->getContent(), true);
return new JsonResponse( data: $data, status: Response::HTTP_OK);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Controller\OgDhcp\Subnet;
use App\Controller\OgBoot\AbstractOgLiveController;
use App\Controller\OgDhcp\AbstractOgDhcpController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[AsController]
class GetCollectionAction extends AbstractOgDhcpController
{
/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function __invoke(HttpClientInterface $httpClient): JsonResponse
{
try {
$response = $httpClient->request('GET', $this->ogDhcpApiUrl.'/opengnsys3/rest/subnets', [
'headers' => [
'accept' => 'application/json',
],
]);
} catch (TransportExceptionInterface $e) {
return new JsonResponse( data: 'An error occurred', status: Response::HTTP_INTERNAL_SERVER_ERROR);
}
$data = json_decode($response->getContent(), true);
return new JsonResponse( data: $data, status: Response::HTTP_OK);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Controller\OgDhcp\Subnet;
class PostAction
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Controller\OgDhcp\Subnet;
class PutAction
{
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Dto\Input;
use ApiPlatform\Metadata\ApiProperty;
use App\Entity\Subnet;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
final class SubnetInput
{
#[Assert\NotBlank(message: 'validators.subnet.netmask.not_blank')]
#[Groups(['subnet:write'])]
#[ApiProperty(description: 'The netmask of the subnet', example: "")]
public ?string $netmask = null;
#[Assert\NotBlank(message: 'validators.subnet.ip_address.not_blank')]
#[Groups(['subnet:write'])]
#[ApiProperty(description: 'The ip address of the subnet', example: "")]
public ?string $ipAddress = null;
#[Assert\NotBlank(message: 'validators.subnet.next_server.not_blank')]
#[Groups(['subnet:write'])]
#[ApiProperty(description: 'The next server of the subnet', example: "")]
public ?string $nextServer = null;
#[Assert\NotBlank(message: 'validators.subnet.boot_file_name.not_blank')]
#[Groups(['subnet:write'])]
#[ApiProperty(description: 'The boot file name of the subnet', example: "")]
public ?string $bootFileName = null;
public function __construct(?Subnet $subnet = null)
{
if (!$subnet) {
return;
}
$this->netmask = $subnet->getNetmask();
$this->ipAddress = $subnet->getIpAddress();
$this->nextServer = $subnet->getNextServer();
$this->bootFileName = $subnet->getBootFileName();
}
public function createOrUpdateEntity(?Subnet $subnet = null): Subnet
{
if (!$subnet) {
$subnet = new Subnet();
}
$subnet->setNetmask($this->netmask);
$subnet->setIpAddress($this->ipAddress);
$subnet->setNextServer($this->nextServer);
$subnet->setBootFileName($this->bootFileName);
return $subnet;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Dto\Output;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Get;
use App\Entity\Subnet;
use Symfony\Component\Serializer\Annotation\Groups;
#[Get(shortName: 'Subnet')]
final class SubnetOutput extends AbstractOutput
{
#[Groups(['subnet:read'])]
public string $netmask;
#[Groups(['subnet:read'])]
public string $ipAddress;
#[Groups(['subnet:read'])]
public string $nextServer;
#[Groups(['subnet:read'])]
public string $bootFileName;
#[Groups(['subnet:read'])]
public \DateTime $createdAt;
#[Groups(['subnet:read'])]
public ?string $createdBy = null;
public function __construct(Subnet $subnet)
{
parent::__construct($subnet);
$this->netmask = $subnet->getNetmask();
$this->ipAddress = $subnet->getIpAddress();
$this->nextServer = $subnet->getNextServer();
$this->bootFileName = $subnet->getBootFileName();
$this->createdAt = $subnet->getCreatedAt();
$this->createdBy = $subnet->getCreatedBy();
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Entity;
use App\Repository\SubnetRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: SubnetRepository::class)]
class Subnet extends AbstractEntity
{
#[ORM\Column(length: 255)]
private ?string $netmask = null;
#[ORM\Column(length: 255)]
private ?string $ipAddress = null;
#[ORM\Column(length: 255)]
private ?string $nextServer = null;
#[ORM\Column(length: 255)]
private ?string $bootFileName = null;
public function getNetmask(): ?string
{
return $this->netmask;
}
public function setNetmask(string $netmask): static
{
$this->netmask = $netmask;
return $this;
}
public function getIpAddress(): ?string
{
return $this->ipAddress;
}
public function setIpAddress(string $ipAddress): static
{
$this->ipAddress = $ipAddress;
return $this;
}
public function getNextServer(): ?string
{
return $this->nextServer;
}
public function setNextServer(string $nextServer): static
{
$this->nextServer = $nextServer;
return $this;
}
public function getBootFileName(): ?string
{
return $this->bootFileName;
}
public function setBootFileName(string $bootFileName): static
{
$this->bootFileName = $bootFileName;
return $this;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Factory;
use App\Entity\Subnet;
use App\Repository\SubnetRepository;
use Zenstruck\Foundry\ModelFactory;
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
use Zenstruck\Foundry\Persistence\Proxy;
use Zenstruck\Foundry\Persistence\ProxyRepositoryDecorator;
/**
* @extends PersistentProxyObjectFactory<Subnet>
*/
final class SubnetFactory extends ModelFactory
{
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
*
* @todo inject services if required
*/
public function __construct()
{
parent::__construct();
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
*
* @todo add your default values here
*/
protected function getDefaults(): array
{
return [
'bootFileName' => self::faker()->text(255),
'createdAt' => self::faker()->dateTime(),
'ipAddress' => self::faker()->text(255),
'netmask' => self::faker()->text(255),
'nextServer' => self::faker()->text(255),
'updatedAt' => self::faker()->dateTime()
];
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
*/
protected function initialize(): self
{
return $this
// ->afterInstantiate(function(Subnet $subnet): void {})
;
}
protected static function getClass(): string
{
return Subnet::class;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Repository;
use App\Entity\Subnet;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Subnet>
*/
class SubnetRepository extends AbstractRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Subnet::class);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\State\Processor;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Validator\ValidatorInterface;
use App\Dto\Input\SubnetInput;
use App\Dto\Output\SubnetOutput;
use App\Repository\SubnetRepository;
use App\Service\OgBoot\PxeBootFile\PostService;
readonly class SubnetProcessor implements ProcessorInterface
{
public function __construct(
private SubnetRepository $subnetRepository,
private ValidatorInterface $validator
)
{
}
/**
* @throws \Exception
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): SubnetOutput|null
{
switch ($operation){
case $operation instanceof Post:
case $operation instanceof Put:
case $operation instanceof Patch:
return $this->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 = []): SubnetOutput
{
if (!($data instanceof SubnetInput)) {
throw new \Exception(sprintf('data is not instance of %s', SubnetInput::class));
}
$entity = null;
if (isset($uriVariables['uuid'])) {
$entity = $this->subnetRepository->findOneByUuid($uriVariables['uuid']);
}
$subnet = $data->createOrUpdateEntity($entity);
$this->validator->validate($subnet);
$this->subnetRepository->save($subnet);
return new SubnetOutput($subnet);
}
private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null
{
$bootFile = $this->subnetRepository->findOneByUuid($uriVariables['uuid']);
$this->subnetRepository->delete($bootFile);
return null;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\State\Provider;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Put;
use ApiPlatform\State\Pagination\TraversablePaginator;
use ApiPlatform\State\ProviderInterface;
use App\Dto\Input\SubnetInput;
use App\Dto\Output\SubnetOutput;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
readonly class SubnetProvider implements ProviderInterface
{
public function __construct(
private ProviderInterface $collectionProvider,
private ProviderInterface $itemProvider
)
{
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
switch ($operation){
case $operation instanceof GetCollection:
return $this->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 SubnetOutput($item);
}
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('Subnet not found');
}
return new SubnetOutput($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 SubnetInput($item) : null;
}
return new SubnetInput();
}
}