Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support passing ResourceRecord objects when creating new records #148

Merged
merged 9 commits into from
Nov 20, 2023
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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.
Expand Down
13 changes: 11 additions & 2 deletions src/Resources/ResourceRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
27 changes: 18 additions & 9 deletions src/Zone.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*
Expand All @@ -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);
Expand Down
101 changes: 101 additions & 0 deletions tests/ZoneTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:[email protected]; ruf=mailto:[email protected]"', $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:[email protected]; ruf=mailto:[email protected]"'))
->setTtl(40),
]);
}

public function testCreateNoResourceRecords(): void
{
$connector = Mockery::mock(Connector::class);
Expand Down