From 3b437d42f46995d72cfd1f573376f1c9d2e0869f Mon Sep 17 00:00:00 2001 From: petr-buchyn Date: Wed, 14 Oct 2020 02:24:12 +0300 Subject: [PATCH] Services, Ingresses, refactoring --- src/Core/Ingress/AbstractIngress.php | 24 +++++ .../Configurator/IngressBackendFactory.php | 61 +++++++++++++ .../Configurator/IngressRulesConfigurator.php | 34 +++++++ src/Core/Ingress/IngressInterface.php | 16 ++++ src/Core/Service/AbstractService.php | 32 +++++++ .../Configurator/ServicePortConfigurator.php | 37 ++++++++ .../Configurator/ServicePortsConfigurator.php | 41 +++++++++ .../Configurator/ServiceTypeConfigurator.php | 56 ++++++++++++ src/Core/Service/IPProtocol.php | 37 ++++++++ src/Core/Service/ServiceInterface.php | 17 ++++ src/Helper/Names/DefaultNamesHelper.php | 10 +++ src/Helper/Names/NamesHelperInterface.php | 1 + src/Registry/AppRegistry.php | 8 ++ src/Registry/ManifestRegistry.php | 27 +++--- src/Registry/Query/ManifestsQuery.php | 88 +++++++++++++++++++ src/ResourceMaker/IngressMaker.php | 44 ++++++++++ src/ResourceMaker/ServiceMaker.php | 61 +++++++++++++ 17 files changed, 578 insertions(+), 16 deletions(-) create mode 100644 src/Core/Ingress/AbstractIngress.php create mode 100644 src/Core/Ingress/Configurator/IngressBackendFactory.php create mode 100644 src/Core/Ingress/Configurator/IngressRulesConfigurator.php create mode 100644 src/Core/Ingress/IngressInterface.php create mode 100644 src/Core/Service/AbstractService.php create mode 100644 src/Core/Service/Configurator/ServicePortConfigurator.php create mode 100644 src/Core/Service/Configurator/ServicePortsConfigurator.php create mode 100644 src/Core/Service/Configurator/ServiceTypeConfigurator.php create mode 100644 src/Core/Service/IPProtocol.php create mode 100644 src/Core/Service/ServiceInterface.php create mode 100644 src/Registry/Query/ManifestsQuery.php create mode 100644 src/ResourceMaker/IngressMaker.php create mode 100644 src/ResourceMaker/ServiceMaker.php diff --git a/src/Core/Ingress/AbstractIngress.php b/src/Core/Ingress/AbstractIngress.php new file mode 100644 index 0000000..871df7c --- /dev/null +++ b/src/Core/Ingress/AbstractIngress.php @@ -0,0 +1,24 @@ +app = $app; + } + + /** + * @param string $serviceName + * @param int|string $servicePort + * + * @return IngressBackend + */ + public function fromServiceNameAndPort(string $serviceName, $servicePort): IngressBackend + { + $servicePort = $this->getPort($servicePort); + $backend = new IngressBackend($serviceName, $servicePort); + $backend + ->setServiceName($serviceName) + ->setServicePort($servicePort); + + return $backend; + } + + /** + * @param string $serviceClass + * @param int|string $servicePort + * + * @return IngressBackend + */ + public function fromServiceClassAndPort(string $serviceClass, $servicePort): IngressBackend + { + $serviceName = $this->app->namesHelper()->byServiceClass($serviceClass); + + return $this->fromServiceNameAndPort($serviceName, $servicePort); + } + + private function getPort($servicePort): IntOrString + { + // TODO change this to int|string typehint when PHP 8.0 is released + if (is_int($servicePort)) { + $servicePort = IntOrString::fromInt($servicePort); + } elseif (is_string($servicePort)) { + $servicePort = IntOrString::fromString($servicePort); + } else { + throw new \TypeError('$servicePort must be an int or a string'); + } + + return $servicePort; + } +} diff --git a/src/Core/Ingress/Configurator/IngressRulesConfigurator.php b/src/Core/Ingress/Configurator/IngressRulesConfigurator.php new file mode 100644 index 0000000..868c044 --- /dev/null +++ b/src/Core/Ingress/Configurator/IngressRulesConfigurator.php @@ -0,0 +1,34 @@ +app = $app; + $this->rules = $rules; + } + + public function addHttpRule(string $path, IngressBackend $backend, string $host = null): void + { + $ingressPath = new HTTPIngressPath($backend); + $ingressPath->setPath($path); + + $rule = new IngressRule(); + if (null !== $host) { + $rule->setHost($host); + } + $rule->http()->paths()->add($ingressPath); + $this->rules->add($rule); + } +} diff --git a/src/Core/Ingress/IngressInterface.php b/src/Core/Ingress/IngressInterface.php new file mode 100644 index 0000000..f9d9e18 --- /dev/null +++ b/src/Core/Ingress/IngressInterface.php @@ -0,0 +1,16 @@ +port = $port; + } + + public function setProtocol(IPProtocol $protocol): self + { + $this->port->setProtocol($protocol->toString()); + + return $this; + } + + public function setName(string $name): self + { + $this->port->setName($name); + + return $this; + } + + public function setNodePort(int $nodePort): self + { + $this->port->setNodePort($nodePort); + + return $this; + } +} diff --git a/src/Core/Service/Configurator/ServicePortsConfigurator.php b/src/Core/Service/Configurator/ServicePortsConfigurator.php new file mode 100644 index 0000000..ecb5245 --- /dev/null +++ b/src/Core/Service/Configurator/ServicePortsConfigurator.php @@ -0,0 +1,41 @@ +ports = $ports; + } + + /** + * @param int $servicePort + * @param int|string $targetPort + * + * @return ServicePortConfigurator + */ + public function add(int $servicePort, $targetPort) + { + // TODO change this to int|string typehint when PHP 8.0 is released + if (is_int($targetPort)) { + $targetPort = IntOrString::fromInt($targetPort); + } elseif (is_string($targetPort)) { + $targetPort = IntOrString::fromString($targetPort); + } else { + throw new \TypeError('$targetPort must be an int or a string'); + } + + $port = new ServicePort($servicePort); + $port->setTargetPort($targetPort); + $this->ports->add($port); + + return new ServicePortConfigurator($port); + } +} diff --git a/src/Core/Service/Configurator/ServiceTypeConfigurator.php b/src/Core/Service/Configurator/ServiceTypeConfigurator.php new file mode 100644 index 0000000..592f4c9 --- /dev/null +++ b/src/Core/Service/Configurator/ServiceTypeConfigurator.php @@ -0,0 +1,56 @@ +service = $service; + } + + public function clusterIP(): void + { + $this->setServiceType(self::TYPE_CLUSTER_IP); + } + + public function nodePort(): void + { + $this->setServiceType(self::TYPE_NODE_PORT); + } + + public function loadBalancer(): void + { + $this->setServiceType(self::TYPE_LOAD_BALANCER); + } + + public function externalName(string $externalName): void + { + $this->setServiceType(self::TYPE_EXTERNAL_NAME); + $this->service->spec()->setExternalName($externalName); + } + + private function setServiceType(string $type): void + { + $existingType = $this->service->spec()->getType(); + if (null !== $existingType && $existingType !== $type) { + throw new \LogicException( + sprintf( + 'Cannot set service type to "%s", it is already set to "%s"', + $type, + $existingType + ) + ); + } + $this->service->spec()->setType($type); + } +} diff --git a/src/Core/Service/IPProtocol.php b/src/Core/Service/IPProtocol.php new file mode 100644 index 0000000..caf3e4c --- /dev/null +++ b/src/Core/Service/IPProtocol.php @@ -0,0 +1,37 @@ +value = $value; + } + + public function toString(): string + { + return $this->value; + } + + public static function tcp(): self + { + return new self(self::TCP); + } + + public static function udp(): self + { + return new self(self::UDP); + } + + public static function sctp(): self + { + return new self(self::SCTP); + } +} diff --git a/src/Core/Service/ServiceInterface.php b/src/Core/Service/ServiceInterface.php new file mode 100644 index 0000000..3eda0aa --- /dev/null +++ b/src/Core/Service/ServiceInterface.php @@ -0,0 +1,17 @@ +byExpectedClassName( + $serviceClass, + ServiceInterface::class, + __METHOD__ + ); + } + private function byExpectedClassName(string $actualClass, string $expectedClass, string $methodName): string { if (!class_exists($actualClass)) { diff --git a/src/Helper/Names/NamesHelperInterface.php b/src/Helper/Names/NamesHelperInterface.php index b1a668d..edbe630 100644 --- a/src/Helper/Names/NamesHelperInterface.php +++ b/src/Helper/Names/NamesHelperInterface.php @@ -12,4 +12,5 @@ public function byManifestClass(string $manifestClass): string; public function byConfigMapClass(string $configMapClass): string; public function bySecretClass(string $configMapClass): string; + public function byServiceClass(string $serviceClass): string; } diff --git a/src/Registry/AppRegistry.php b/src/Registry/AppRegistry.php index f88d0ae..977b991 100644 --- a/src/Registry/AppRegistry.php +++ b/src/Registry/AppRegistry.php @@ -54,4 +54,12 @@ public function get(string $appName): AppInterface } return $this->apps[$appName]; } + + /** + * @return array|string[] + */ + public function names(): array + { + return array_keys($this->apps); + } } diff --git a/src/Registry/ManifestRegistry.php b/src/Registry/ManifestRegistry.php index 36fd469..ab3bb2c 100644 --- a/src/Registry/ManifestRegistry.php +++ b/src/Registry/ManifestRegistry.php @@ -4,6 +4,7 @@ use Dealroadshow\K8S\Framework\App\AppInterface; use Dealroadshow\K8S\Framework\Core\ManifestInterface; +use Dealroadshow\K8S\Framework\Registry\Query\ManifestsQuery; class ManifestRegistry { @@ -21,31 +22,25 @@ public function __construct(iterable $manifests) } /** - * @param AppInterface $app - * - * @param string|null $className * @return iterable|ManifestInterface[] */ - public function byApp(AppInterface $app, string $className = null): iterable + public function all(): iterable { - $reflection = new \ReflectionObject($app); - - return $this->byNamespacePrefix($reflection->getNamespaceName(), $className); + return $this->manifests; } /** - * @param string $namespacePrefix + * @param AppInterface $app * - * @param string|null $className * @return iterable|ManifestInterface[] */ - public function byNamespacePrefix(string $namespacePrefix, string $className = null): iterable + public function byApp(AppInterface $app): iterable + { + return $this->query()->app($app)->execute(); + } + + public function query(): ManifestsQuery { - foreach ($this->manifests as $manifest) { - $class = get_class($manifest); - if (str_starts_with($class, $namespacePrefix) && (null === $className || $manifest instanceof $className)) { - yield $manifest; - } - } + return new ManifestsQuery($this); } } diff --git a/src/Registry/Query/ManifestsQuery.php b/src/Registry/Query/ManifestsQuery.php new file mode 100644 index 0000000..a73cb6b --- /dev/null +++ b/src/Registry/Query/ManifestsQuery.php @@ -0,0 +1,88 @@ +registry = $registry; + $this->result = $registry->all(); + $this->closures = []; + } + + public function app(AppInterface $app): self + { + $reflection = new \ReflectionObject($app); + + return $this->namespacePrefix($reflection->getNamespaceName()); + } + + public function namespacePrefix(string $prefix): self + { + return $this->addClosure( + fn (ManifestInterface $manifest): bool => str_starts_with(get_class($manifest), $prefix) + ); + } + + public function instancesOf(string $className): self + { + return $this->addClosure( + fn (ManifestInterface $manifest): bool => $manifest instanceof $className + ); + } + + public function name(string $name): self + { + return $this->addClosure( + fn (ManifestInterface $manifest): bool => $manifest::name() === $name + ); + } + + /** + * @return iterable|ManifestInterface[] + */ + public function execute(): iterable + { + foreach ($this->result as $manifest) { + foreach ($this->closures as $closure) { + if (!$closure($manifest)) { + continue 2; + } + } + yield $manifest; + } + } + + public function getFirstResult(): ?ManifestInterface + { + foreach ($this->execute() as $manifest) { + return $manifest; + } + + return null; + } + + private function addClosure(\Closure $closure): self + { + $this->closures[] = $closure; + + return $this; + } +} diff --git a/src/ResourceMaker/IngressMaker.php b/src/ResourceMaker/IngressMaker.php new file mode 100644 index 0000000..1ff8eb1 --- /dev/null +++ b/src/ResourceMaker/IngressMaker.php @@ -0,0 +1,44 @@ +metadataHelper()->configureMeta($manifest, $ingress); + + $backendFactory = new IngressBackendFactory($app); + $backend = $manifest->backend($backendFactory); + if (null !== $backend) { + $ingress->spec()->setBackend($backend); + } + + $rules = new IngressRulesConfigurator($app, $ingress->spec()->rules()); + $manifest->rules($rules, $backendFactory); + + $manifest->configureIngress($ingress); + + return $ingress; + } + + protected function supportsClass(): string + { + return IngressInterface::class; + } +} diff --git a/src/ResourceMaker/ServiceMaker.php b/src/ResourceMaker/ServiceMaker.php new file mode 100644 index 0000000..c9546e1 --- /dev/null +++ b/src/ResourceMaker/ServiceMaker.php @@ -0,0 +1,61 @@ +metadataHelper()->configureMeta($manifest, $service); + + $type = new ServiceTypeConfigurator($service); + $manifest->type($type); + + $ports = new ServicePortsConfigurator($service->spec()->ports()); + $manifest->ports($ports); + $this->validatePorts($service->spec()->ports(), $manifest, $app); + + $manifest->selector($service->spec()->selector()); + $manifest->configureService($service); + + return $service; + } + + private function validatePorts(ServicePortList $ports, ServiceInterface $manifest, AppInterface $app) + { + $ports = $ports->all(); + if (count($ports) > 1) { + foreach ($ports as $port) { + if (null === $port->getName()) { + throw new \LogicException( + sprintf( + 'Multiple ports specified for service "%s", therefore all ports must have name', + $app->namesHelper()->byManifest($manifest) + ) + ); + } + } + } + } +}