Commit 185faa92 authored by Ludwig Ruderstaller's avatar Ludwig Ruderstaller
Browse files

fixes #904414897791793

parent f5dd78e7
Pipeline #17736 passed with stage
in 1 minute and 45 seconds
......@@ -13,12 +13,18 @@ declare(strict_types=1);
namespace Cwd\PowerDNSClient;
use Doctrine\Common\Annotations\AnnotationReader;
use GuzzleHttp\Psr7\Request;
use Http\Client\HttpClient;
use Http\Discovery\HttpClientDiscovery;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
......@@ -45,8 +51,11 @@ class Client
$this->apiKey = $apiKey;
$this->apiUri = sprintf('%s/%s', $apiHost, $this->basePath);
$normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter(), null, new ReflectionExtractor());
$this->serializer = new Serializer([new DateTimeNormalizer(), $normalizer], ['json' => new JsonEncoder()]);
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
$normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter(), new PropertyAccessor(), new ReflectionExtractor(), $discriminator);
$this->serializer = new Serializer([new DateTimeNormalizer(), new ArrayDenormalizer(), $normalizer], ['json' => new JsonEncoder()]);
}
/**
......@@ -76,10 +85,6 @@ class Client
$responseBody = $response->getBody()->getContents();
$responseData = json_decode($responseBody);
//if (getenv('DEBUG')) {
// dump([$uri, $method, isset($responseData->error) ? $responseData->error : [], $response->getStatusCode()]);
//}
if ($response->getStatusCode() >= 300 && isset($responseData->error)) {
throw new \LogicException(sprintf('Error on %s request %s: %s', $method, $uri, $responseData->error));
} elseif ($response->getStatusCode() >= 300) {
......@@ -106,7 +111,7 @@ class Client
foreach ($dataObject as $data) {
$result[] = $this->serializer->denormalize($data, $hydrationClass, null, [
ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true,
ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => false,
]);
}
......
......@@ -32,9 +32,9 @@ abstract class AbstractEndpoint
->getValidator();
}
public function validateEntity($entity)
public function validateEntity($entity, $groups = null)
{
$violations = $this->validator->validate($entity);
$violations = $this->validator->validate($entity, null, $groups);
if (count($violations) > 0) {
throw new ValidationException(
......
......@@ -20,6 +20,47 @@ class ZonesEndpoint extends AbstractEndpoint
const ENDPOINT_LIST = 'servers/%s/zones';
const ENDPOINT_ELEMENT = 'servers/%s/zones/%s';
public function update(Zone $zone, $lazyLoad = false, $hydrationClass = Zone::class): ?Zone
{
$this->validateEntity($zone, ['UPDATE']);
//$zone->setRrsets([]);
$payload = $this->getClient()->getSerializer()->serialize($zone, 'json', ['groups' => ['REPLACE']]);
$this->getClient()->call($payload, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zone->getId()), null, false, 'PUT');
if ($lazyLoad) {
return $this->get($zone->getId(), $hydrationClass);
}
return null;
}
public function updateRRSets(Zone $zone, $lazyLoad = false, $hydrationClass = Zone::class): ?Zone
{
$this->validateEntity($zone, ['UPDATE']);
// Remove RecordSets which are not changed
$newSet = [];
/** @var Zone\RRSet $rrset */
foreach ($zone->getRrsets() as $rrset) {
if (null !== $rrset->getChangetype()) {
$newSet[] = $rrset;
}
}
$payload = $this->getClient()->getSerializer()->serialize(['rrsets' => $newSet], 'json', ['groups' => ['REPLACE']]);
$this->getClient()->call($payload, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zone->getId()), null, false, 'PATCH');
if ($lazyLoad) {
return $this->get($zone->getId(), $hydrationClass);
}
return null;
}
/**
* @param string|Zone $zoneId
*/
......@@ -36,9 +77,9 @@ class ZonesEndpoint extends AbstractEndpoint
{
$rrsets = ($rrsets) ? 'true' : 'false';
$this->validateEntity($zone);
$this->validateEntity($zone, ['CREATE']);
$payload = $this->getClient()->getSerializer()->serialize($zone, 'json');
$payload = $this->getClient()->getSerializer()->serialize($zone, 'json', ['groups' => ['CREATE']]);
return $this->getClient()->call($payload, sprintf(self::ENDPOINT_LIST, $this->defaultServerId), $hydrationClass, false, 'POST', ['rrsets' => $rrsets]);
}
......@@ -73,4 +114,49 @@ class ZonesEndpoint extends AbstractEndpoint
return $this->getClient()->call(null, sprintf(self::ENDPOINT_LIST, $this->defaultServerId), $hydrationClass, true, 'GET', $queryParams);
}
public function axfrRetrieve($zoneId)
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
}
$this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId).'/axfr-retrieve', null, false, 'PUT');
}
public function notify($zoneId)
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
}
$this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId).'/notify', null, false, 'PUT');
}
public function export($zoneId)
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
}
return $this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId).'/export', null, false, 'GET');
}
public function check($zoneId)
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
}
return $this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId).'/check', null, false, 'GET');
}
public function rectify($zoneId)
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
}
return $this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId).'/rectify', null, false, 'PUT');
}
}
......@@ -13,6 +13,8 @@ declare(strict_types=1);
namespace Cwd\PowerDNSClient\Model;
use Cwd\PowerDNSClient\Model\Zone\RRSet;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Cwd\PowerDNSClient\Validator\Constraints as DNSAssert;
......@@ -23,59 +25,116 @@ class Zone
const KIND_SLAVE = 'Slave';
const KIND_NATIVE = 'Native';
/** @var string|null */
/**
* @var string|null
* @Groups({"CREATE", "DELETE"})
*/
private $id;
/**
* @var string
* @Assert\NotBlank()
* @DNSAssert\HasDotPostfix()
* @Assert\NotBlank(groups={"CREATE"})
* @DNSAssert\HasDotPostfix(groups={"CREATE"})
* @Groups({"CREATE", "DELETE"})
*/
private $name;
/**
* @var string
* @Assert\NotBlank()
* @Assert\Choice(
* choices= {"Zone"}
* choices= {"Zone"},
* groups={"CREATE", "UPDATE"}
* )
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $type = self::TYPE;
/** @var string|null */
/**
* @var string|null
* @Groups({"CREATE", "DELETE"})
*/
private $url;
/**
* @var string|null
* @Assert\Choice(
* choices = {"Master", "Slave", "Native"}
* choices = {"Master", "Slave", "Native"},
* groups={"CREATE", "UPDATE"}
* )
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $kind;
/** @var RRSet[] */
/**
* @var array
* @Assert\Valid(groups={"CREATE", "UPDATE"})
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $rrsets = [];
/** @var int|null */
private $serial;
/** @var int|null */
private $notifiedSerial;
/** @var string[] */
/**
* @var string[]
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $masters = [];
/** @var bool */
/**
* @var bool
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $dnssec = false;
/** @var string|null */
/**
* @var string|null
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $nsec3param;
/** @var bool */
/**
* @var bool
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $nsec3narrow = false;
/** @var bool */
/**
* @var bool
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $predisgned = false;
/** @var string|null */
/**
* @var string|null
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $soaEdit;
/** @var string|null */
/**
* @var string|null
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $soaEditApi;
/** @var bool */
/**
* @var bool
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $apiRectify = false;
/** @var string|null */
/**
* @var string|null
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $zone;
/** @var string|null */
/**
* @var string|null
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $account;
/** @var string[] */
/**
* @var string[]
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $nameservers = [];
/**
......@@ -179,7 +238,7 @@ class Zone
}
/**
* @return RRSet[]
* @return array<RRSet>
*/
public function getRrsets(): array
{
......@@ -187,7 +246,7 @@ class Zone
}
/**
* @param RRSet[] $rrsets
* @param array $rrsets
*
* @return Zone
*/
......@@ -198,6 +257,13 @@ class Zone
return $this;
}
public function addRrset(RRSet $rrset): Zone
{
$this->rrsets[] = $rrset;
return $this;
}
/**
* @return int|null
*/
......
......@@ -13,12 +13,23 @@ declare(strict_types=1);
namespace Cwd\PowerDNSClient\Model\Zone;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
class Comment
{
/** @var string */
/**
* @var string
* @Assert\NotBlank(groups={"CREATE", "UPDATE"})
* @Groups({"REPLACE", "CREATE"})
*/
private $content;
/** @var string */
/**
* @var string
* @Groups({"REPLACE", "CREATE"})
* @Assert\NotBlank(groups={"CREATE", "UPDATE"})
*/
private $account;
/** @var int */
......@@ -47,7 +58,7 @@ class Comment
/**
* @return string
*/
public function getAccount(): string
public function getAccount(): ?string
{
return $this->account;
}
......@@ -57,7 +68,7 @@ class Comment
*
* @return Comment
*/
public function setAccount(string $account): Comment
public function setAccount(?string $account): Comment
{
$this->account = $account;
......@@ -67,7 +78,7 @@ class Comment
/**
* @return int
*/
public function getModifiedAt(): int
public function getModifiedAt(): ?int
{
return $this->modifiedAt;
}
......
......@@ -13,19 +13,67 @@ declare(strict_types=1);
namespace Cwd\PowerDNSClient\Model\Zone;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Cwd\PowerDNSClient\Validator\Constraints as DNSAssert;
class RRSet
{
/** @var string */
const TYPE_REPLACE = 'REPLACE';
const TYPE_DELETE = 'DELETE';
const TYPE_CREATE = 'REPLACE'; // Yes this is by design!
/**
* @var string
* @Assert\NotBlank(groups={"CREATE", "UPDATE"})
* @DNSAssert\HasDotPostfix(groups={"CREATE", "UPDATE"})
*
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $name;
/** @var string */
/**
* @var string
* @Assert\NotBlank(groups={"CREATE", "UPDATE"})
* @Assert\Choice(
* groups={"CREATE", "UPDATE"},
* choices={
* "A", "AAAA", "AFSDB", "ALIAS", "CAA", "CERT", "CDNSKEY", "CDS", "CNAME", "DNSKEY", "DNAME", "DS", "HINFO",
* "KEY", "LOC", "MX", "NAPTR", "NS", "NSEC, NSEC3, NSEC3PARAM", "OPENPGPKEY", "PTR", "RP", "RRSIG", "SOA",
* "SPF", "SSHFP", "SRV", "TKEY, TSIG", "TLSA", "SMIMEA", "TXT", "URI"
* }
* )
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $type;
/** @var int */
/**
* @var int
* @Groups({"REPLACE", "CREATE"})
*/
private $ttl;
/** @var string */
/**
* @var string
* @Assert\Choice(
* choices={"REPLACE", "DELETE", "CREATE"},
* groups={"CREATE", "UPDATE"}
* )
* @Groups({"REPLACE", "DELETE"})
*/
private $changetype;
/** @var Record[] */
/**
* @var Record[]
* @Assert\Valid(groups={"CREATE", "UPDATE"})
* @Groups({"REPLACE", "CREATE"})
*/
private $records = [];
/** @var Comment[] */
/**
* @var Comment[]
* @Assert\Valid(groups={"CREATE", "UPDATE"})
* @Groups({"REPLACE", "CREATE"})
*/
private $comments = [];
/**
......@@ -91,7 +139,7 @@ class RRSet
/**
* @return string
*/
public function getChangetype(): string
public function getChangetype(): ?string
{
return $this->changetype;
}
......@@ -101,7 +149,7 @@ class RRSet
*
* @return RRSet
*/
public function setChangetype(string $changetype): RRSet
public function setChangetype(?string $changetype): RRSet
{
$this->changetype = $changetype;
......@@ -128,6 +176,13 @@ class RRSet
return $this;
}
public function addRecord(Record $record): RRSet
{
$this->records[] = $record;
return $this;
}
/**
* @return Comment[]
*/
......@@ -143,8 +198,16 @@ class RRSet
*/
public function setComments(array $comments): RRSet
{
\Webmozart\Assert\Assert::allIsInstanceOf($comments, Comment::class);
$this->comments = $comments;
return $this;
}
public function addComment(Comment $comment): RRSet
{
$this->comments[] = $comment;
return $this;
}
}
......@@ -13,13 +13,28 @@ declare(strict_types=1);
namespace Cwd\PowerDNSClient\Model\Zone;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
class Record
{
/** @var string */
/**
* @var string
* @Assert\NotBlank(groups={"CREATE", "UPDATE"})
* @Groups({"REPLACE", "CREATE"})
*/
private $content;
/** @var bool */
/**
* @var bool
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $disabled = false;
/** @var */
/**
* @var bool
* @Groups({"REPLACE", "CREATE", "DELETE"})
*/
private $setPtr = false;
/**
......
......@@ -84,11 +84,57 @@ class ZonesEndpointTest extends AbstractTest
return $this->getClient()->zones()->create($zone, true);
}
public function testWrongRRSetType()
{
$zone = (new Zone())
->setName('example.com.')
->setKind(Zone::KIND_MASTER)
->addRrset(
(new Zone\RRSet())->setName('www.example.com.')
->setType('WRONG')
->setTtl(3600)
->addRecord(
(new Zone\Record())->setContent('127.0.0.1')
->setDisabled(false)
)
->addComment(
(new Zone\Comment())->setContent('Test Test')
->setAccount('Max Mustermann')
)
)
;
$this->expectException(ValidationException::class);
$this->getClient()->zones()->create($zone, true);
}
public function testCreate()
{
$zone = (new Zone())
->setName('example.com.')
->setKind(Zone::KIND_MASTER)
->addRrset(
(new Zone\RRSet())->setName('www.example.com.')
->setType('A')
->setTtl(3600)
->addRecord(
(new Zone\Record())->setContent('127.0.0.1')
->setDisabled(false)
)
->addComment(
(new Zone\Comment())->setContent('Test Test')
->setAccount('Max Mustermann')
)
)
->addRrset((new Zone\RRSet())->setName('delete.example.com.')
->setType('A')
->setTtl(3600)
->addRecord(
(new Zone\Record())->setContent('127.0.0.1')
->setDisabled(false)
)
->addComment((new Zone\Comment())->setContent('test')->setAccount('Maxi'))
)
;
$zone = $this->getClient()->zones()->create($zone, true);
......@@ -102,6 +148,7 @@ class ZonesEndpointTest extends AbstractTest
$zone = (new Zone())
->setName('example-slave.com.')
->setKind(Zone::KIND_SLAVE)
->setMasters(['127.0.0.2'])
;
$zone = $this->getClient()->zones()->create($zone, true);
......@@ -110,48 +157,239 @@ class ZonesEndpointTest extends AbstractTest
return $zone;
}
public function testCreateExisting()
/**
* @depends testCreate
*/
public function testGetById(Zone $zone)
{
$this->expectException(\LogicException::class);
$newZone = $this->getClient()->zones()->get($zone->getId());
$zone = (new Zone())
->setName('example.com.')