diff --git a/README.md b/README.md index 0644efc..5300d3f 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ Basic example how to create a new DNS zone and insert a few DNS records. ```php use Exonet\Powerdns\Powerdns; use Exonet\Powerdns\RecordType; +use Exonet\Powerdns\Resources\ResourceRecord; +use Exonet\Powerdns\Resources\Record; // Initialize the Powerdns client. $powerdns = new Powerdns('127.0.0.1', 'powerdns_secret_string'); @@ -33,6 +35,12 @@ $zone->create([ ['type' => RecordType::A, 'content' => '127.0.0.1', 'ttl' => 60, 'name' => '@'], ['type' => RecordType::A, 'content' => '127.0.0.1', 'ttl' => 60, 'name' => 'www'], ]); + +// OR use the Object-based way +$zone->create([ + (new ResourceRecord())->setType(RecordType::A)->setRecord('127.0.0.1')->setName('@')->setTtl(60), + (new ResourceRecord())->setType(RecordType::A)->setRecord((new Record())->setContent('127.0.0.1'))->setName('@')->setTtl(60), +]); ``` See the [examples](examples) directory for more. diff --git a/src/Resources/ResourceRecord.php b/src/Resources/ResourceRecord.php index 1573455..1d467b4 100644 --- a/src/Resources/ResourceRecord.php +++ b/src/Resources/ResourceRecord.php @@ -271,6 +271,15 @@ public function getShortName(): string */ public function setName(string $name): self { + if ($this->getZone() !== null) { + $name = str_replace('@', $this->getZone()->getCanonicalName(), $name); + + // If the name of the record doesn't end in the zone name, append the zone name to it. + if (substr($name, -strlen($this->getZone()->getCanonicalName())) !== $this->getZone()->getCanonicalName()) { + $name = sprintf('%s.%s', $name, $this->getZone()->getCanonicalName()); + } + } + $this->name = $name; return $this; @@ -398,9 +407,9 @@ public function setType(string $type): self /** * Get the zone object for this ResourceRecord. * - * @return Zone The zone for this ResourceRecord. + * @return Zone|null The zone for this ResourceRecord. */ - public function getZone(): Zone + public function getZone(): ?Zone { return $this->zone; } diff --git a/src/Zone.php b/src/Zone.php index cb2c4ac..0ee0d15 100644 --- a/src/Zone.php +++ b/src/Zone.php @@ -3,7 +3,6 @@ namespace Exonet\Powerdns; use Exonet\Powerdns\Exceptions\InvalidNsec3Param; -use Exonet\Powerdns\Resources\Record; use Exonet\Powerdns\Resources\ResourceRecord; use Exonet\Powerdns\Resources\ResourceSet; use Exonet\Powerdns\Transformers\DnssecTransformer; @@ -21,12 +20,12 @@ class Zone extends AbstractZone * resource records will be created in a single call to the PowerDNS server. If $name is a string, a single resource * record is created. * - * @param mixed[]|string $name The resource record name. - * @param string $type The type of the resource record. - * @param mixed[]|string $content The content of the resource record. When passing a multidimensional array, - * multiple records are created for this resource record. - * @param int $ttl The TTL. - * @param array|mixed[] $comments The comment to assign to the record. + * @param mixed[]|ResourceRecord|ResourceRecord[]|string $name The resource record name. + * @param string $type The type of the resource record. + * @param mixed[]|string $content The content of the resource record. When passing a multidimensional array, + * multiple records are created for this resource record. + * @param int $ttl The TTL. + * @param array|mixed[] $comments The comment to assign to the record. * * @throws Exceptions\InvalidRecordType If the given type is invalid. * @@ -37,10 +36,20 @@ public function create($name, string $type = '', $content = '', int $ttl = 3600, if (is_array($name)) { $resourceRecords = []; foreach ($name as $item) { - $resourceRecords[] = $this->make($item['name'], $item['type'], $item['content'], $item['ttl'] ?? $ttl, $item['comments'] ?? []); + if ($item instanceof ResourceRecord) { + $item->setZone($this)->setName($item->getName()); + $resourceRecords[] = $item; + } else { + $resourceRecords[] = $this->make($item['name'], $item['type'], $item['content'], $item['ttl'] ?? $ttl, $item['comments'] ?? []); + } } } else { - $resourceRecords = [$this->make($name, $type, $content, $ttl, $comments)]; + if ($name instanceof ResourceRecord) { + $name->setZone($this)->setName($name->getName()); + $resourceRecords = [$name]; + } else { + $resourceRecords = [$this->make($name, $type, $content, $ttl, $comments)]; + } } return $this->patch($resourceRecords); diff --git a/tests/ZoneTest.php b/tests/ZoneTest.php index 045c5e6..1956791 100644 --- a/tests/ZoneTest.php +++ b/tests/ZoneTest.php @@ -3,6 +3,10 @@ namespace Exonet\Powerdns\tests; use Exonet\Powerdns\Connector; +use Exonet\Powerdns\RecordType; +use Exonet\Powerdns\Resources\Comment; +use Exonet\Powerdns\Resources\Record; +use Exonet\Powerdns\Resources\ResourceRecord; use Exonet\Powerdns\Resources\Zone as ZoneResource; use Exonet\Powerdns\Transformers\RRSetTransformer; use Exonet\Powerdns\Zone; @@ -61,6 +65,34 @@ public function testCreateSingleResourceRecord(): void $zone->create('test', 'A', '127.0.0.1', 10); } + public function testCreateSingleResourceRecordFromClass(): void + { + $connector = Mockery::mock(Connector::class); + $connector->shouldReceive('patch')->withArgs(['zones/test.nl.', Mockery::on(function (RRSetTransformer $transformer) { + $data = $transformer->transform(); + + $this->assertSame('test.test.nl.', $data->rrsets[0]->name); + $this->assertSame('A', $data->rrsets[0]->type); + $this->assertSame(10, $data->rrsets[0]->ttl); + $this->assertSame('127.0.0.1', $data->rrsets[0]->records[0]->content); + + return true; + })]); + + $zone = new Zone($connector, 'test.nl'); + $rr = (new ResourceRecord()) + ->setName('test.test.nl.') + ->setType(RecordType::A) + ->setRecord('127.0.0.1') + ->setTtl(10) + ->setComments([ + (new Comment())->setContent('Hello')->setAccount('root')->setModifiedAt(1), + (new Comment())->setContent('Power')->setAccount('admin')->setModifiedAt(2), + (new Comment())->setContent('DNS')->setAccount('superuser')->setModifiedAt(3), + ]); + $zone->create($rr); + } + public function testCreateMultipleResourceRecords(): void { $connector = Mockery::mock(Connector::class); @@ -106,6 +138,75 @@ public function testCreateMultipleResourceRecords(): void ]); } + public function testCreateMultipleResourceRecordsFromClass(): void + { + $connector = Mockery::mock(Connector::class); + $connector->shouldReceive('patch')->withArgs(['zones/test.nl.', Mockery::on(function (RRSetTransformer $transformer) { + $data = $transformer->transform(); + + $this->assertSame('test.test.nl.', $data->rrsets[0]->name); + $this->assertSame('A', $data->rrsets[0]->type); + $this->assertSame(10, $data->rrsets[0]->ttl); + $this->assertSame('127.0.0.1', $data->rrsets[0]->records[0]->content); + + $this->assertSame('test.nl.', $data->rrsets[1]->name); + $this->assertSame('A', $data->rrsets[1]->type); + $this->assertSame(20, $data->rrsets[1]->ttl); + $this->assertSame('127.0.0.1', $data->rrsets[1]->records[0]->content); + + $this->assertSame('test.nl.', $data->rrsets[2]->name); + $this->assertSame('MX', $data->rrsets[2]->type); + $this->assertSame(30, $data->rrsets[2]->ttl); + $this->assertSame('10 mail01.test.nl.', $data->rrsets[2]->records[0]->content); + $this->assertSame('20 mail02.test.nl.', $data->rrsets[2]->records[1]->content); + + $this->assertSame('test02.test.nl.', $data->rrsets[3]->name); + $this->assertSame('A', $data->rrsets[3]->type); + $this->assertSame(40, $data->rrsets[3]->ttl); + $this->assertSame('127.0.0.1', $data->rrsets[3]->records[0]->content); + + $this->assertSame('test03.test.nl.', $data->rrsets[4]->name); + $this->assertSame('TXT', $data->rrsets[4]->type); + $this->assertSame(40, $data->rrsets[4]->ttl); + $this->assertSame('"v=DMARC1; p=none; rua=mailto:info@test.nl; ruf=mailto:info@test.nl"', $data->rrsets[4]->records[0]->content); + + return true; + })]); + + $zone = new Zone($connector, 'test.nl'); + $zone->create([ + (new ResourceRecord()) + ->setName('test') + ->setType(RecordType::A) + ->setRecord((new Record()) + ->setContent('127.0.0.1')) + ->setTtl(10), + (new ResourceRecord()) + ->setName('@') + ->setType(RecordType::A) + ->setRecord((new Record())->setContent('127.0.0.1')) + ->setTtl(20), + (new ResourceRecord()) + ->setName('@') + ->setType(RecordType::MX) + ->setRecords([ + (new Record())->setContent('10 mail01.test.nl.'), + (new Record())->setContent('20 mail02.test.nl.'), + ]) + ->setTtl(30), + (new ResourceRecord()) + ->setName('test02') + ->setType(RecordType::A) + ->setRecord((new Record())->setContent('127.0.0.1')) + ->setTtl(40), + + (new ResourceRecord()) + ->setName('test03') + ->setType(RecordType::TXT)->setRecord((new Record())->setContent('"v=DMARC1; p=none; rua=mailto:info@test.nl; ruf=mailto:info@test.nl"')) + ->setTtl(40), + ]); + } + public function testCreateNoResourceRecords(): void { $connector = Mockery::mock(Connector::class);