Commit 4b1c03c8 authored by Ludwig Ruderstaller's avatar Ludwig Ruderstaller
Browse files

Merge branch 'develop' into 'master'

First Public Release

See merge request !1
parents 069f4995 c9be42fa
Pipeline #18021 passed with stage
in 2 minutes and 19 seconds
......@@ -12,7 +12,7 @@ variables:
APP_ENV: test
services:
- name: dockerhub.cwd.at/docker/powerdns:0-1
- name: dockerhub.cwd.at/docker/powerdns:0.3
alias: powerdns
before_script:
- export COMPOSER_CACHE_DIR="$(pwd -P)/.composer-cache"
......@@ -22,7 +22,7 @@ variables:
#- vendor/bin/php-cs-fixer fix --dry-run --config=.php_cs
# - php -d zend.enable_gc=0 bin/phpunit -c ./phpunit.xml.dist --coverage-html=build/coverage --coverage-xml=build/logs/coverage --log-junit=build/logs/phpunit.xml --coverage-text --colors=never
#- bin/behat --format=progress --verbose --stop-on-failure
- vendor/bin/phpunit --coverage-html build/coverage --log-junit=build/logs/phpunit.xml
- vendor/bin/phpunit --coverage-html build/coverage --exclude-group performance --log-junit=build/logs/phpunit.xml --coverage-text --colors=never
stage: test
cache:
key: "${CI_PROJECT_PATH_SLUG}-cache"
......
build:
nodes:
analysis:
project_setup:
override:
- 'true'
tests:
override:
- php-scrutinizer-run
-
command: phpcs-run
use_website_config: true
tests: false
filter:
excluded_paths:
- 'tests/*'
checks:
php: true
coding_style:
php: { }
\ No newline at end of file
......@@ -13,12 +13,17 @@ 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;
......@@ -27,10 +32,11 @@ use GuzzleHttp\Client as GuzzleClient;
class Client
{
private $basePath = 'api/v1';
private $serverId = 'localhost';
private $apiKey;
/** @var HttpClient */
private $apiUri;
/** @var GuzzleClient */
private $client;
/** @var Serializer */
......@@ -38,14 +44,18 @@ class Client
public function __construct($apiHost, $apiKey, ?GuzzleClient $client = null)
{
$this->apiKey = $apiKey;
$this->apiUri = sprintf('%s/%s', $apiHost, $this->basePath);
if (null === $client) {
//$this->client = new GuzzleClient(['base_uri' => $this->apiUri]);
$this->client = HttpClientDiscovery::find();
}
$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()]);
}
/**
......@@ -63,8 +73,11 @@ class Client
*/
public function call($payload = null, $uri, $hydrationClass = null, $isList = false, $method = 'GET', array $queryParams = [])
{
$uri = sprintf('%s/%s?%s', $this->apiUri, $uri, http_build_query($queryParams));
$uri = rtrim($uri, '/');
$uri = rtrim(sprintf('%s/%s', $this->apiUri, $uri), '/');
if (count($queryParams) > 0) {
$uri .= '?'.http_build_query($queryParams);
}
$request = new Request($method, $uri, [
'X-API-Key' => $this->apiKey,
......@@ -72,23 +85,22 @@ class Client
], $payload);
$response = $this->client->sendRequest($request);
$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) {
}
if ($response->getStatusCode() >= 300) {
$message = isset($responseData->message) ?? 'Unknown';
throw new \Exception(sprintf('Error on request %s: %s', $response->getStatusCode(), $message));
}
if (null !== $hydrationClass && class_exists($hydrationClass)) {
return $this->denormalizeObject($hydrationClass, $responseData, $isList);
} elseif (null !== $hydrationClass && !class_exists($hydrationClass)) {
}
if (null !== $hydrationClass && !class_exists($hydrationClass)) {
throw new \Exception(sprintf('HydrationClass (%s) does not exist', $hydrationClass));
}
......@@ -105,7 +117,7 @@ class Client
foreach ($dataObject as $data) {
$result[] = $this->serializer->denormalize($data, $hydrationClass, null, [
ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true,
ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => false,
]);
}
......
<?php
/*
* This file is part of the CwdPowerDNS Client
*
* (c) 2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\PowerDNSClient;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class CwdPowerDNSClient extends Bundle
{
}
\ No newline at end of file
<?php
/*
* This file is part of the CwdPowerDNS Client
*
* (c) 2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\PowerDNSClient\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('cwd_power_dns_client');
$rootNode->children()
->variableNode('uri')->defaultValue('http://localhost')->end()
->variableNode('api_key')->defaultValue(null)->end()
->variableNode('default_server')->defaultValue('localhost')->end()
->end();
return $treeBuilder;
}
}
\ No newline at end of file
<?php
/*
* This file is part of the CwdPowerDNS Client
*
* (c) 2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\PowerDNSClient\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
class CwdPowerDNSClientExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('cwd_power_dns_client.uri', $config['uri']);
$container->setParameter('cwd_power_dns_client.api_key', $config['api_key']);
$container->setParameter('cwd_power_dns_client.default_server', $config['default_server']);
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.yaml');
}
}
......@@ -32,13 +32,12 @@ abstract class AbstractEndpoint
->getValidator();
}
public function validateEntity($entity)
public function validateEntity($entity, $groups = null): bool
{
$violations = $this->validator->validate($entity);
if (count($violations) > 0) {
$violations = $this->validator->validate($entity, null, $groups);
if (\count($violations) > 0) {
throw new ValidationException(
sprintf('Entity %s does not validate to spezification', get_class($entity)),
sprintf('Entity %s does not validate to spezification', \get_class($entity)),
0,
null,
$violations
......
<?php
/*
* This file is part of the CwdPowerDNS Client
*
* (c) 2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\PowerDNSClient\Endpoints;
use Cwd\PowerDNSClient\Client;
use Cwd\PowerDNSClient\Model\Cryptokey;
class CryptokeysEndpoint extends AbstractEndpoint
{
use UriHelperTrait;
private const ENDPOINT_LIST = 'servers/%s/zones/%s/cryptokeys';
private const ENDPOINT_ELEMENT = 'servers/%s/zones/%s/cryptokeys/%s';
private $zoneId;
public function __construct(Client $client, $defaultServerId, $zoneId)
{
$this->zoneId = $zoneId;
parent::__construct($client, $defaultServerId);
}
public function all($hydrationClass = Cryptokey::class): array
{
$uri = sprintf(self::ENDPOINT_LIST, $this->defaultServerId, $this->zoneId);
return $this->getClient()->call(null, $uri, $hydrationClass, true, 'GET');
}
public function get($cryptokey, $hydrationClass = Cryptokey::class): ?Cryptokey
{
$cryptokeyId = $cryptokey;
if ($cryptokey instanceof Cryptokey) {
$cryptokeyId = $cryptokey->getId();
}
return $this->getClient()->call(null, $this->uriHelper($cryptokeyId), $hydrationClass, false, 'GET');
}
public function create(Cryptokey $cryptokey, $hydrationClass = Cryptokey::class): ?Cryptokey
{
$this->validateEntity($cryptokey, ['CREATE']);
$uri = sprintf(self::ENDPOINT_LIST, $this->defaultServerId, $this->zoneId);
$payload = $this->getClient()->getSerializer()->serialize($cryptokey, 'json');
return $this->getClient()->call($payload, $uri, $hydrationClass, false, 'POST');
}
public function activate($cryptokey, $lacyLoad = true, $hydrationClass = Cryptokey::class): ?Cryptokey
{
return $this->setStatus(true, $cryptokey, $lacyLoad, $hydrationClass);
}
public function deactivate($cryptokey, $lacyLoad = true, $hydrationClass = Cryptokey::class): ?Cryptokey
{
return $this->setStatus(false, $cryptokey, $lacyLoad, $hydrationClass);
}
private function setStatus(bool $active, $cryptokey, $lacyLoad = true, $hydrationClass = Cryptokey::class): ?Cryptokey
{
$cryptokeyId = $cryptokey;
if ($cryptokey instanceof Cryptokey) {
$cryptokeyId = $cryptokey->getId();
}
$payload = $this->getClient()->getSerializer()->serialize(['active' => $active], 'json');
$this->getClient()->call($payload, $this->uriHelper($cryptokeyId), null, false, 'PUT');
if ($lacyLoad) {
return $this->get($cryptokeyId, $hydrationClass);
}
return null;
}
public function delete($cryptokey): void
{
$cryptokeyId = $cryptokey;
if ($cryptokey instanceof Cryptokey) {
$cryptokeyId = $cryptokey->getId();
}
$this->getClient()->call(null, $this->uriHelper($cryptokeyId), null, false, 'DELETE');
}
}
<?php
/*
* This file is part of the CwdPowerDNS Client
*
* (c) 2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\PowerDNSClient\Endpoints;
use Cwd\PowerDNSClient\Client;
use Cwd\PowerDNSClient\Model\Metadata;
class MetadataEndpoint extends AbstractEndpoint
{
use UriHelperTrait;
private const ENDPOINT_LIST = 'servers/%s/zones/%s/metadata';
private const ENDPOINT_ELEMENT = 'servers/%s/zones/%s/metadata/%s';
private $zoneId;
public function __construct(Client $client, $defaultServerId, $zoneId)
{
$this->zoneId = $zoneId;
parent::__construct($client, $defaultServerId);
}
/**
* @param string $hydrationClass
*
* @return Metadata[]
*
* @throws \Http\Client\Exception
*/
public function all($hydrationClass = Metadata::class): array
{
$uri = sprintf(self::ENDPOINT_LIST, $this->defaultServerId, $this->zoneId);
return $this->getClient()->call(null, $uri, $hydrationClass, true, 'GET');
}
/**
* @param string $kind
* @param string $hydrationClass
*
* @return Metadata
*
* @throws \Http\Client\Exception
*/
public function get(string $kind, string $hydrationClass = Metadata::class): ?Metadata
{
return $this->getClient()->call(null, $this->uriHelper($kind), $hydrationClass, false, 'GET');
}
/**
* @param Metadata $metadata
* @param bool $lacyLoad
* @param string $hydrationClass
*
* @return Metadata|null
*
* @throws \Http\Client\Exception
*/
public function create(Metadata $metadata, $lacyLoad = true, $hydrationClass = Metadata::class): ?Metadata
{
$this->validateEntity($metadata, ['CREATE']);
$uri = sprintf(self::ENDPOINT_LIST, $this->defaultServerId, $this->zoneId);
$payload = $this->getClient()->getSerializer()->serialize($metadata, 'json');
$this->getClient()->call($payload, $uri, null, false, 'POST');
if ($lacyLoad) {
return $this->get($metadata->getKind(), $hydrationClass);
}
return null;
}
/**
* @param Metadata $metadata
* @param bool $lacyLoad
* @param string $hydrationClass
*
* @return Metadata|null
*
* @throws \Http\Client\Exception
*/
public function update(Metadata $metadata, $lacyLoad = true, string $hydrationClass = Metadata::class): ?Metadata
{
$this->validateEntity($metadata, ['UPDATE']);
$payload = $this->getClient()->getSerializer()->serialize($metadata, 'json');
$this->getClient()->call($payload, $this->uriHelper($metadata->getKind()), null, false, 'PUT');
if ($lacyLoad) {
return $this->get($metadata->getKind(), $hydrationClass);
}
return null;
}
/**
* @param Metadata|string $metadata
*
* @throws \Http\Client\Exception
*/
public function delete($metadata): void
{
$kind = $metadata;
if ($metadata instanceof Metadata) {
$kind = $metadata->getKind();
}
$this->getClient()->call(null, $this->uriHelper($kind), null, false, 'DELETE');
}
}
......@@ -13,12 +13,21 @@ declare(strict_types=1);
namespace Cwd\PowerDNSClient\Endpoints;
use Cwd\PowerDNSClient\Model\CacheFlushResult;
use Cwd\PowerDNSClient\Model\Config;
use Cwd\PowerDNSClient\Model\Server;
class ServersEndpoint extends AbstractEndpoint
{
const ENDPOINT = 'servers/%s';
private const ENDPOINT = 'servers/%s';
/**
* @param null|string $serverId
*
* @return Server
*
* @throws \Http\Client\Exception
*/
public function get(?string $serverId = null): Server
{
if (null === $serverId) {
......@@ -29,7 +38,7 @@ class ServersEndpoint extends AbstractEndpoint
}
/**
* @return Servers[]
* @return Server[]
*
* @throws \Http\Client\Exception
*/
......@@ -48,4 +57,21 @@ class ServersEndpoint extends AbstractEndpoint
// Result is different - denormalize by hand
return $this->getClient()->call(null, sprintf(self::ENDPOINT, $this->defaultServerId).'/statistics', null, false, 'GET');
}
public function cacheFlush(string $domain): CacheFlushResult
{
return $this->getClient()->call(null, sprintf(self::ENDPOINT.'/cache/flush', $this->defaultServerId), CacheFlushResult::class, false, 'PUT', ['domain' => $domain]);
}
/**
* @return Config[]
*
* @throws \Http\Client\Exception
*/
public function config(): array
{
$uri = sprintf(self::ENDPOINT, $this->defaultServerId).'/config';
return $this->getClient()->call(null, $uri, Config::class, true, 'GET');
}
}
<?php
/*
* This file is part of the CwdPowerDNS Client
*
* (c) 2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\PowerDNSClient\Endpoints;
trait UriHelperTrait
{
protected function uriHelper($kind): string
{
return sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $this->zoneId, $kind);
}
}
......@@ -13,17 +13,76 @@ declare(strict_types=1);
namespace Cwd\PowerDNSClient\Endpoints;
use Cwd\PowerDNSClient\Model\SearchResult;
use Cwd\PowerDNSClient\Model\Zone;
class ZonesEndpoint extends AbstractEndpoint
{
const ENDPOINT_LIST = 'servers/%s/zones';
const ENDPOINT_ELEMENT = 'servers/%s/zones/%s';
protected const ENDPOINT_LIST = 'servers/%s/zones';
protected const ENDPOINT_ELEMENT = 'servers/%s/zones/%s';
/**
* @param string|Zone $zoneId
* @param Zone $zone
* @param bool $lazyLoad
* @param string $hydrationClass
*
* @return Zone|null
*
* @throws \Http\Client\Exception
*/
public function update(Zone $zone, $lazyLoad = false, string $hydrationClass = Zone::class): ?Zone
{
$this->validateEntity($zone, ['UPDATE']);
$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;
}
/**
* @param Zone $zone
* @param bool $lazyLoad
* @param string $hydrationClass
*
* @return Zone|null
*
* @throws \Http\Client\Exception
*/
public function updateRRSets(Zone $zone, bool $lazyLoad = false, string $hydrationClass = Zone::class): ?Zone