From c8200336129e312f765a47047f96770cd22fea0f Mon Sep 17 00:00:00 2001 From: Pepa Martinec Date: Fri, 5 Apr 2024 21:59:06 +0200 Subject: [PATCH 1/2] WIP Deployment support --- .../provisioning/local/kubernetes/main.tf | 8 + .../src/ApiClient/DeploymentsApiClient.php | 26 + .../GenericClientFacadeFactory.php | 2 + .../src/KubernetesApiClientFacade.php | 32 +- .../tests/KubernetesApiClientFacadeTest.php | 1531 +++++++++-------- 5 files changed, 830 insertions(+), 769 deletions(-) create mode 100644 libs/k8s-client/src/ApiClient/DeploymentsApiClient.php diff --git a/libs/k8s-client/provisioning/local/kubernetes/main.tf b/libs/k8s-client/provisioning/local/kubernetes/main.tf index 6de50e4b7..a2a1fc258 100644 --- a/libs/k8s-client/provisioning/local/kubernetes/main.tf +++ b/libs/k8s-client/provisioning/local/kubernetes/main.tf @@ -50,6 +50,14 @@ resource "kubernetes_role" "k8s_client" { ] verbs = ["get", "list", "delete", "create", "patch", "deletecollection"] } + + rule { + api_groups = ["apps"] + resources = [ + "deployments", + ] + verbs = ["get", "list", "delete", "create", "patch", "deletecollection"] + } } resource "kubernetes_role_binding" "k8s_client" { diff --git a/libs/k8s-client/src/ApiClient/DeploymentsApiClient.php b/libs/k8s-client/src/ApiClient/DeploymentsApiClient.php new file mode 100644 index 000000000..6f9620192 --- /dev/null +++ b/libs/k8s-client/src/ApiClient/DeploymentsApiClient.php @@ -0,0 +1,26 @@ + + */ +class DeploymentsApiClient extends BaseNamespaceApiClient +{ + public function __construct(KubernetesApiClient $apiClient) + { + parent::__construct( + $apiClient, + new DeploymentsApi(), + DeploymentList::class, + Deployment::class, + ); + } +} diff --git a/libs/k8s-client/src/ClientFacadeFactory/GenericClientFacadeFactory.php b/libs/k8s-client/src/ClientFacadeFactory/GenericClientFacadeFactory.php index c296f27cd..19be3161b 100644 --- a/libs/k8s-client/src/ClientFacadeFactory/GenericClientFacadeFactory.php +++ b/libs/k8s-client/src/ClientFacadeFactory/GenericClientFacadeFactory.php @@ -5,6 +5,7 @@ namespace Keboola\K8sClient\ClientFacadeFactory; use Keboola\K8sClient\ApiClient\ConfigMapsApiClient; +use Keboola\K8sClient\ApiClient\DeploymentsApiClient; use Keboola\K8sClient\ApiClient\EventsApiClient; use Keboola\K8sClient\ApiClient\IngressesApiClient; use Keboola\K8sClient\ApiClient\PersistentVolumeClaimsApiClient; @@ -62,6 +63,7 @@ public function createClusterClient( return new KubernetesApiClientFacade( $this->logger, new ConfigMapsApiClient($apiClient), + new DeploymentsApiClient($apiClient), new EventsApiClient($apiClient), new IngressesApiClient($apiClient), new PersistentVolumeClaimsApiClient($apiClient), diff --git a/libs/k8s-client/src/KubernetesApiClientFacade.php b/libs/k8s-client/src/KubernetesApiClientFacade.php index d81688a8c..bf1853598 100644 --- a/libs/k8s-client/src/KubernetesApiClientFacade.php +++ b/libs/k8s-client/src/KubernetesApiClientFacade.php @@ -5,6 +5,7 @@ namespace Keboola\K8sClient; use Keboola\K8sClient\ApiClient\ConfigMapsApiClient; +use Keboola\K8sClient\ApiClient\DeploymentsApiClient; use Keboola\K8sClient\ApiClient\EventsApiClient; use Keboola\K8sClient\ApiClient\IngressesApiClient; use Keboola\K8sClient\ApiClient\PersistentVolumeClaimsApiClient; @@ -14,6 +15,7 @@ use Keboola\K8sClient\ApiClient\ServicesApiClient; use Keboola\K8sClient\Exception\ResourceNotFoundException; use Keboola\K8sClient\Exception\TimeoutException; +use Kubernetes\Model\Io\K8s\Api\Apps\V1\Deployment; use Kubernetes\Model\Io\K8s\Api\Core\V1\ConfigMap; use Kubernetes\Model\Io\K8s\Api\Core\V1\Event; use Kubernetes\Model\Io\K8s\Api\Core\V1\PersistentVolume; @@ -29,6 +31,7 @@ use RuntimeException; use Throwable; +// phpcs:disable Generic.Files.LineLength.MaxExceeded class KubernetesApiClientFacade { private const LIST_INTERNAL_PAGE_SIZE = 100; @@ -38,6 +41,7 @@ class KubernetesApiClientFacade public function __construct( private readonly LoggerInterface $logger, private readonly ConfigMapsApiClient $configMapApiClient, + private readonly DeploymentsApiClient $deploymentsApiClient, private readonly EventsApiClient $eventsApiClient, private readonly IngressesApiClient $ingressesApiClient, private readonly PersistentVolumeClaimsApiClient $persistentVolumeClaimsApiClient, @@ -48,6 +52,7 @@ public function __construct( ) { $this->resourceTypeClientMap = [ ConfigMap::class => $this->configMapApiClient, + Deployment::class => $this->deploymentsApiClient, Event::class => $this->eventsApiClient, PersistentVolumeClaim::class => $this->persistentVolumeClaimsApiClient, Pod::class => $this->podsApiClient, @@ -78,6 +83,11 @@ public function events(): EventsApiClient return $this->eventsApiClient; } + public function deployments(): DeploymentsApiClient + { + return $this->deploymentsApiClient; + } + public function persistentVolumeClaims(): PersistentVolumeClaimsApiClient { return $this->persistentVolumeClaimsApiClient; @@ -99,7 +109,7 @@ public function persistentVolumes(): PersistentVolumesApiClient } /** - * @phpstan-template T of ConfigMap|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume + * @phpstan-template T of ConfigMap|Deployment|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume * @phpstan-param class-string $resourceType * @phpstan-return T */ @@ -125,8 +135,8 @@ public function get(string $resourceType, string $name, array $queries = []) * new Ingress(...), * ]) * - * @param array $resources - * @return (ConfigMap|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume)[] + * @param array $resources + * @return (ConfigMap|Deployment|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume)[] */ public function createModels(array $resources, array $queries = []): array { @@ -153,7 +163,7 @@ public function createModels(array $resources, array $queries = []): array * new PersistentVolume(...), * ]) * - * @param array $resources + * @param array $resources * @return Status[] */ public function deleteModels(array $resources, ?DeleteOptions $deleteOptions = null, array $queries = []): array @@ -169,7 +179,7 @@ public function deleteModels(array $resources, ?DeleteOptions $deleteOptions = n } /** - * @param array $resources + * @param array $resources */ public function waitWhileExists(array $resources, float $timeout = INF): void { @@ -209,7 +219,7 @@ public function waitWhileExists(array $resources, float $timeout = INF): void } /** - * @template T of ConfigMap|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume + * @template T of ConfigMap|Deployment|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume * @param class-string $resourceType * @return iterable */ @@ -235,7 +245,7 @@ public function listMatching(string $resourceType, array $queries = []): iterabl * Resources are delete sequentially by API type. If some delete request fails, the error is logged and other APIs * are still called. Finally, the last exception is re-thrown. * - * @template T of ConfigMap|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume + * @template T of ConfigMap|Deployment|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume * @param array{ * resourceTypes?: class-string[] * } $queries @@ -274,7 +284,7 @@ public function deleteAllMatching(?DeleteOptions $deleteOptions = null, array $q } /** - * @template T of ConfigMap|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume + * @template T of ConfigMap|Deployment|Event|PersistentVolumeClaim|Pod|Secret|Service|Ingress|PersistentVolume * @param class-string $resourceType */ public function checkResourceExists(string $resourceType, string $resourceName): bool @@ -291,6 +301,7 @@ public function checkResourceExists(string $resourceType, string $resourceName): /** * @param class-string $resourceType * @return ($resourceType is class-string ? ConfigMapsApiClient : + * ($resourceType is class-string ? DeploymentsApiClient : * ($resourceType is class-string ? EventsApiClient : * ($resourceType is class-string ? PersistentVolumeClaimsApiClient : * ($resourceType is class-string ? PodsApiClient : @@ -298,10 +309,9 @@ public function checkResourceExists(string $resourceType, string $resourceName): * ($resourceType is class-string ? ServicesApiClient : * ($resourceType is class-string ? IngressesApiClient : * ($resourceType is class-string ? PersistentVolumesApiClient : - * never)))))))) + * never))))))))) */ - // phpcs:ignore Generic.Files.LineLength.MaxExceeded - private function getApiForResource(string $resourceType): ConfigMapsApiClient|EventsApiClient|PersistentVolumeClaimsApiClient|PodsApiClient|SecretsApiClient|ServicesApiClient|IngressesApiClient|PersistentVolumesApiClient + private function getApiForResource(string $resourceType): ConfigMapsApiClient|DeploymentsApiClient|EventsApiClient|PersistentVolumeClaimsApiClient|PodsApiClient|SecretsApiClient|ServicesApiClient|IngressesApiClient|PersistentVolumesApiClient { if (!array_key_exists($resourceType, $this->resourceTypeClientMap)) { throw new RuntimeException(sprintf( diff --git a/libs/k8s-client/tests/KubernetesApiClientFacadeTest.php b/libs/k8s-client/tests/KubernetesApiClientFacadeTest.php index 7986c1c1d..8f4147ff2 100644 --- a/libs/k8s-client/tests/KubernetesApiClientFacadeTest.php +++ b/libs/k8s-client/tests/KubernetesApiClientFacadeTest.php @@ -5,6 +5,7 @@ namespace Keboola\K8sClient\Tests; use Keboola\K8sClient\ApiClient\ConfigMapsApiClient; +use Keboola\K8sClient\ApiClient\DeploymentsApiClient; use Keboola\K8sClient\ApiClient\EventsApiClient; use Keboola\K8sClient\ApiClient\IngressesApiClient; use Keboola\K8sClient\ApiClient\PersistentVolumeClaimsApiClient; @@ -47,6 +48,7 @@ protected function setUp(): void public function testApisAccessors(): void { $configMapsApiClient = $this->createMock(ConfigMapsApiClient::class); + $deploymentsApiClient = $this->createMock(DeploymentsApiClient::class); $eventsApiClient = $this->createMock(EventsApiClient::class); $persistentVolumeClaimClient = $this->createMock(PersistentVolumeClaimsApiClient::class); $podsApiClient = $this->createMock(PodsApiClient::class); @@ -58,6 +60,7 @@ public function testApisAccessors(): void $facade = new KubernetesApiClientFacade( $this->logger, $configMapsApiClient, + $deploymentsApiClient, $eventsApiClient, $ingressesApiClient, $persistentVolumeClaimClient, @@ -97,6 +100,9 @@ public function testGetPod(): void $secretsApiClient = $this->createMock(SecretsApiClient::class); $secretsApiClient->expects(self::never())->method(self::anything()); + $deploymentsApiClient = $this->createMock(DeploymentsApiClient::class); + $deploymentsApiClient->expects(self::never())->method(self::anything()); + $eventsApiClient = $this->createMock(EventsApiClient::class); $eventsApiClient->expects(self::never())->method(self::anything()); @@ -112,6 +118,7 @@ public function testGetPod(): void $facade = new KubernetesApiClientFacade( $this->logger, $this->createMock(ConfigMapsApiClient::class), + $deploymentsApiClient, $eventsApiClient, $ingressesApiClient, $this->createMock(PersistentVolumeClaimsApiClient::class), @@ -146,6 +153,9 @@ public function testGetSecret(): void ->willReturn($returnedSecret) ; + $deploymentsApiClient = $this->createMock(DeploymentsApiClient::class); + $deploymentsApiClient->expects(self::never())->method(self::anything()); + $eventsApiClient = $this->createMock(EventsApiClient::class); $eventsApiClient->expects(self::never())->method(self::anything()); @@ -161,6 +171,7 @@ public function testGetSecret(): void $facade = new KubernetesApiClientFacade( $this->logger, $this->createMock(ConfigMapsApiClient::class), + $deploymentsApiClient, $eventsApiClient, $ingressesApiClient, $this->createMock(PersistentVolumeClaimsApiClient::class), @@ -191,6 +202,9 @@ public function testGetEvent(): void $secretsApiClient = $this->createMock(SecretsApiClient::class); $secretsApiClient->expects(self::never())->method(self::anything()); + $deploymentsApiClient = $this->createMock(DeploymentsApiClient::class); + $deploymentsApiClient->expects(self::never())->method(self::anything()); + $eventsApiClient = $this->createMock(EventsApiClient::class); $eventsApiClient->expects(self::once()) ->method('get') @@ -210,6 +224,7 @@ public function testGetEvent(): void $facade = new KubernetesApiClientFacade( $this->logger, $this->createMock(ConfigMapsApiClient::class), + $deploymentsApiClient, $eventsApiClient, $ingressesApiClient, $this->createMock(PersistentVolumeClaimsApiClient::class), @@ -223,762 +238,762 @@ public function testGetEvent(): void self::assertSame($returnedEvent, $result); } - public function testCreateModels(): void - { - // request & result represent the same resource but are different class instances - $podRequest1 = new Pod(['metadata' => ['name' => 'pod1']]); - $podRequest2 = new Pod(['metadata' => ['name' => 'pod2']]); - $secretRequest3 = new Secret(['metadata' => ['name' => 'secret3']]); - $eventRequest4 = new Event(['metadata' => ['name' => 'event4']]); - $serviceRequest5 = new Service(['metadata' => ['name' => 'service5']]); - $ingressRequest6 = new Ingress(['metadata' => ['name' => 'ingress6']]); - $persistentVolumeRequest7 = new PersistentVolume(['metadata' => ['name' => 'persistentVolume7']]); - - $podResult1 = new Pod(['metadata' => ['name' => 'pod1']]); - $podResult2 = new Pod(['metadata' => ['name' => 'pod2']]); - $secretResult3 = new Secret(['metadata' => ['name' => 'secret3']]); - $eventResult4 = new Event(['metadata' => ['name' => 'event4']]); - $serviceResult5 = new Service(['metadata' => ['name' => 'service5']]); - $ingressResult6 = new Ingress(['metadata' => ['name' => 'ingress6']]); - $persistentVolumeResult7 = new PersistentVolume(['metadata' => ['name' => 'persistentVolume7']]); - - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::exactly(2)) - ->method('create') - ->withConsecutive( - [$podRequest1, []], - [$podRequest2, []], - ) - ->willReturnOnConsecutiveCalls( - $podResult1, - $podResult2, - $secretResult3, - ) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::once()) - ->method('create') - ->with($secretRequest3, []) - ->willReturn($secretResult3) - ; - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::once()) - ->method('create') - ->with($eventRequest4, []) - ->willReturn($eventResult4) - ; - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::once()) - ->method('create') - ->with($serviceRequest5, []) - ->willReturn($serviceResult5) - ; - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::once()) - ->method('create') - ->with($ingressRequest6, []) - ->willReturn($ingressResult6) - ; - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::once()) - ->method('create') - ->with($persistentVolumeRequest7, []) - ->willReturn($persistentVolumeResult7) - ; - - $facade = new KubernetesApiClientFacade( - $this->logger, - $this->createMock(ConfigMapsApiClient::class), - $eventsApiClient, - $ingressesApiClient, - $this->createMock(PersistentVolumeClaimsApiClient::class), - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $result = $facade->createModels([ - $podRequest1, - $podRequest2, - $secretRequest3, - $eventRequest4, - $serviceRequest5, - $ingressRequest6, - $persistentVolumeRequest7, - ]); - - self::assertSame([ - $podResult1, - $podResult2, - $secretResult3, - $eventResult4, - $serviceResult5, - $ingressResult6, - $persistentVolumeResult7, - ], $result); - } - - public function testCreateModelsErrorHandling(): void - { - $pod1 = new Pod(['metadata' => ['name' => 'pod1']]); - $pod2 = new Pod(['metadata' => ['name' => 'pod2']]); - $pod3 = new Pod(['metadata' => ['name' => 'pod3']]); - - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::exactly(2)) - ->method('create') - ->withConsecutive( - [$pod1, []], - [$pod2, []], - ) - ->will(self::onConsecutiveCalls( - self::returnArgument(0), - self::throwException(new RuntimeException('Can\'t create Pod')), - )) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::never())->method(self::anything()); - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::never())->method(self::anything()); - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::never())->method(self::anything()); - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::never())->method(self::anything()); - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::never())->method(self::anything()); - - $facade = new KubernetesApiClientFacade( - $this->logger, - $this->createMock(ConfigMapsApiClient::class), - $eventsApiClient, - $ingressesApiClient, - $this->createMock(PersistentVolumeClaimsApiClient::class), - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Can\'t create Pod'); - - $facade->createModels([$pod1, $pod2, $pod3]); - } - - public function testDeleteModels(): void - { - // request & result represent the same resource but are different class instances - $podRequest1 = new Pod(['metadata' => ['name' => 'pod1']]); - $podRequest2 = new Pod(['metadata' => ['name' => 'pod2']]); - $secretRequest3 = new Secret(['metadata' => ['name' => 'secret3']]); - $eventRequest4 = new Event(['metadata' => ['name' => 'event4']]); - $serviceRequest5 = new Service(['metadata' => ['name' => 'service5']]); - $ingressRequest6 = new Ingress(['metadata' => ['name' => 'ingress6']]); - $persistentVolumeRequest7 = new PersistentVolume(['metadata' => ['name' => 'persistentVolume7']]); - - $podResult1 = new Status(['metadata' => ['name' => 'pod1']]); - $podResult2 = new Status(['metadata' => ['name' => 'pod2']]); - $secretResult3 = new Status(['metadata' => ['name' => 'secret3']]); - $eventResult4 = new Status(['metadata' => ['name' => 'event4']]); - $serviceResult5 = new Status(['metadata' => ['name' => 'service5']]); - $ingressResult6 = new Status(['metadata' => ['name' => 'ingress6']]); - $persistentVolumeResult7 = new Status(['metadata' => ['name' => 'persistentVolume7']]); - - $deleteOptions = new DeleteOptions(); - - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::exactly(2)) - ->method('delete') - ->withConsecutive( - ['pod1', $deleteOptions, []], - ['pod2', $deleteOptions, []], - ) - ->willReturnOnConsecutiveCalls( - $podResult1, - $podResult2, - ) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::once()) - ->method('delete') - ->with('secret3', $deleteOptions, []) - ->willReturn($secretResult3) - ; - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::once()) - ->method('delete') - ->with('event4', $deleteOptions, []) - ->willReturn($eventResult4) - ; - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::once()) - ->method('delete') - ->with('service5', $deleteOptions, []) - ->willReturn($serviceResult5) - ; - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::once()) - ->method('delete') - ->with('ingress6', $deleteOptions, []) - ->willReturn($ingressResult6) - ; - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::once()) - ->method('delete') - ->with('persistentVolume7', $deleteOptions, []) - ->willReturn($persistentVolumeResult7) - ; - - $facade = new KubernetesApiClientFacade( - $this->logger, - $this->createMock(ConfigMapsApiClient::class), - $eventsApiClient, - $ingressesApiClient, - $this->createMock(PersistentVolumeClaimsApiClient::class), - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $result = $facade->deleteModels([ - $podRequest1, - $podRequest2, - $secretRequest3, - $eventRequest4, - $serviceRequest5, - $ingressRequest6, - $persistentVolumeRequest7, - ], $deleteOptions); - - self::assertSame([ - $podResult1, - $podResult2, - $secretResult3, - $eventResult4, - $serviceResult5, - $ingressResult6, - $persistentVolumeResult7, - ], $result); - } - - public function testDeleteModelsErrorHandling(): void - { - $pod1 = new Pod(['metadata' => ['name' => 'pod1']]); - $pod2 = new Pod(['metadata' => ['name' => 'pod2']]); - $pod3 = new Pod(['metadata' => ['name' => 'pod3']]); - - $deleteOptions = new DeleteOptions(); - - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::exactly(2)) - ->method('delete') - ->withConsecutive( - ['pod1', $deleteOptions, []], - ['pod2', $deleteOptions, []], - ) - ->will(self::onConsecutiveCalls( - new Status(['metadata' => ['name' => 'pod1']]), - self::throwException(new RuntimeException('Can\'t delete Pod')), - )) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::never())->method(self::anything()); - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::never())->method(self::anything()); - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::never())->method(self::anything()); - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::never())->method(self::anything()); - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::never())->method(self::anything()); - - $facade = new KubernetesApiClientFacade( - $this->logger, - $this->createMock(ConfigMapsApiClient::class), - $eventsApiClient, - $ingressesApiClient, - $this->createMock(PersistentVolumeClaimsApiClient::class), - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Can\'t delete Pod'); - - $facade->deleteModels([$pod1, $pod2, $pod3], $deleteOptions); - } - - public function testWaitWhileExists(): void - { - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::exactly(4)) - ->method('get') - ->withConsecutive( - // first round check both pods, pod1 still exists, pod2 does not exist - ['pod1'], - ['pod2'], - // second round checks remaining pod1 - ['pod1'], - // third round checks remaining pod1 - ['pod1'], - ) - ->will(self::onConsecutiveCalls( - new Pod(['metadata' => ['name' => 'pod1']]), - self::throwException(new ResourceNotFoundException('Pod doesn\'t exist', null)), - new Pod(['metadata' => ['name' => 'pod1']]), - self::throwException(new ResourceNotFoundException('Pod doesn\'t exist', null)), - )) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::never())->method(self::anything()); - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::never())->method(self::anything()); - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::never())->method(self::anything()); - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::never())->method(self::anything()); - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::never())->method(self::anything()); - - $facade = new KubernetesApiClientFacade( - $this->logger, - $this->createMock(ConfigMapsApiClient::class), - $eventsApiClient, - $ingressesApiClient, - $this->createMock(PersistentVolumeClaimsApiClient::class), - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $facade->waitWhileExists([ - new Pod(['metadata' => ['name' => 'pod1']]), - new Pod(['metadata' => ['name' => 'pod2']]), - ]); - } - - public function testWaitWhileExistsTimeout(): void - { - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient - ->method('get') - ->willReturnCallback(fn($podName) => new Pod(['metadata' => ['name' => $podName]])) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::never())->method(self::anything()); - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::never())->method(self::anything()); - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::never())->method(self::anything()); - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::never())->method(self::anything()); - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::never())->method(self::anything()); - - $facade = new KubernetesApiClientFacade( - $this->logger, - $this->createMock(ConfigMapsApiClient::class), - $eventsApiClient, - $ingressesApiClient, - $this->createMock(PersistentVolumeClaimsApiClient::class), - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $startTime = microtime(true); - try { - $facade->waitWhileExists([ - new Pod(['metadata' => ['name' => 'pod1']]), - new Pod(['metadata' => ['name' => 'pod2']]), - ], 3); - self::fail('Expected TimeoutException was not thrown'); - } catch (TimeoutException) { - } - $endTime = microtime(true); - - self::assertEqualsWithDelta(3, $endTime - $startTime, 1); - } - - public function testListMatching(): void - { - $pod1 = new Pod(['metadata' => ['name' => 'pod-1']]); - $pod2 = new Pod(['metadata' => ['name' => 'pod-2']]); - $pod3 = new Pod(['metadata' => ['name' => 'pod-3']]); - - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::exactly(2)) - ->method('list') - ->withConsecutive( - [['labelSelector' => 'app=my', 'limit' => 100]], - [['labelSelector' => 'app=my', 'limit' => 100, 'continue' => 'foo']], - ) - ->willReturnOnConsecutiveCalls( - new PodList([ - 'metadata' => [ - 'continue' => 'foo', - ], - 'items' => [$pod1, $pod2], - ]), - new PodList([ - 'items' => [$pod3], - ]), - ) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::never())->method(self::anything()); - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::never())->method(self::anything()); - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::never())->method(self::anything()); - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::never())->method(self::anything()); - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::never())->method(self::anything()); - - $facade = new KubernetesApiClientFacade( - $this->logger, - $this->createMock(ConfigMapsApiClient::class), - $eventsApiClient, - $ingressesApiClient, - $this->createMock(PersistentVolumeClaimsApiClient::class), - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $result = $facade->listMatching(Pod::class, ['labelSelector' => 'app=my']); - self::assertEquals([$pod1, $pod2, $pod3], [...$result]); - } - - public function testListMatchingWithCustomPageSize(): void - { - $pod1 = new Pod(['metadata' => ['name' => 'pod-1']]); - $pod2 = new Pod(['metadata' => ['name' => 'pod-2']]); - $pod3 = new Pod(['metadata' => ['name' => 'pod-3']]); - - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::exactly(2)) - ->method('list') - ->withConsecutive( - [['labelSelector' => 'app=my', 'limit' => 5]], - [['labelSelector' => 'app=my', 'limit' => 5, 'continue' => 'foo']], - ) - ->willReturnOnConsecutiveCalls( - new PodList([ - 'metadata' => [ - 'continue' => 'foo', - ], - 'items' => [$pod1, $pod2], - ]), - new PodList([ - 'items' => [$pod3], - ]), - ) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::never())->method(self::anything()); - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::never())->method(self::anything()); - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::never())->method(self::anything()); - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::never())->method(self::anything()); - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::never())->method(self::anything()); - - $facade = new KubernetesApiClientFacade( - $this->logger, - $this->createMock(ConfigMapsApiClient::class), - $eventsApiClient, - $ingressesApiClient, - $this->createMock(PersistentVolumeClaimsApiClient::class), - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $result = $facade->listMatching(Pod::class, ['labelSelector' => 'app=my', 'limit' => 5]); - self::assertEquals([$pod1, $pod2, $pod3], [...$result]); - } - - public function testDeleteAllMatching(): void - { - $deleteOptions = new DeleteOptions(); - $deleteQuery = ['labelSelector' => 'app=my-app']; - - $configMapsApiClient = $this->createMock(ConfigMapsApiClient::class); - $configMapsApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $pvClaimApiClient = $this->createMock(PersistentVolumeClaimsApiClient::class); - $pvClaimApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::never())->method(self::anything()); - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $facade = new KubernetesApiClientFacade( - $this->logger, - $configMapsApiClient, - $eventsApiClient, - $ingressesApiClient, - $pvClaimApiClient, - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $facade->deleteAllMatching($deleteOptions, $deleteQuery); - } - - public function testDeleteAllMatchingWithResourceTypesFilter(): void - { - $deleteOptions = new DeleteOptions(); - $deleteQuery = ['labelSelector' => 'app=my-app']; - - $configMapsApiClient = $this->createMock(ConfigMapsApiClient::class); - $configMapsApiClient->expects(self::never())->method(self::anything()); - - $pvClaimApiClient = $this->createMock(PersistentVolumeClaimsApiClient::class); - $pvClaimApiClient->expects(self::never())->method(self::anything()); - - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::never())->method(self::anything()); - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::never())->method(self::anything()); - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::never())->method(self::anything()); - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::never())->method(self::anything()); - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::never())->method(self::anything()); - - $facade = new KubernetesApiClientFacade( - $this->logger, - $configMapsApiClient, - $eventsApiClient, - $ingressesApiClient, - $pvClaimApiClient, - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - $facade->deleteAllMatching($deleteOptions, ['resourceTypes' => [Secret::class], ...$deleteQuery]); - } - - public function testDeleteAllMatchingErrorHandling(): void - { - $deleteOptions = new DeleteOptions(); - $deleteQuery = ['labelSelector' => 'app=my-app']; - - $configMapsApiClient = $this->createMock(ConfigMapsApiClient::class); - $configMapsApiClient->expects(self::once()) - ->method('deleteCollection') - ->willThrowException(new RuntimeException('Config map delete failed')) - ; - - $pvClaimApiClient = $this->createMock(PersistentVolumeClaimsApiClient::class); - $pvClaimApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ->willThrowException(new RuntimeException('Pod delete failed')) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $eventsApiClient = $this->createMock(EventsApiClient::class); - $eventsApiClient->expects(self::never())->method(self::anything()); - - $servicesApiClient = $this->createMock(ServicesApiClient::class); - $servicesApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $ingressesApiClient = $this->createMock(IngressesApiClient::class); - $ingressesApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); - $persistentVolumesApiClient->expects(self::once()) - ->method('deleteCollection') - ->with($deleteOptions, $deleteQuery) - ; - - $facade = new KubernetesApiClientFacade( - $this->logger, - $configMapsApiClient, - $eventsApiClient, - $ingressesApiClient, - $pvClaimApiClient, - $persistentVolumesApiClient, - $podsApiClient, - $secretsApiClient, - $servicesApiClient, - ); - - try { - $facade->deleteAllMatching($deleteOptions, $deleteQuery); - $this->fail('RuntimeException should be thrown on deleteAllMatching call.'); - } catch (RuntimeException $e) { - self::assertEquals('Pod delete failed', $e->getMessage()); - } - - $records = $this->loggerTestHandler->getRecords(); - self::assertCount(2, $records); - - $record = $records[0]; - self::assertEquals(400, $record['level']); - self::assertEquals('DeleteCollection request has failed', $record['message']); - self::assertArrayHasKey('exception', $record['context']); - self::assertInstanceOf(RuntimeException::class, $record['context']['exception']); - self::assertSame('Config map delete failed', $record['context']['exception']->getMessage()); - - $record = $records[1]; - self::assertEquals(400, $record['level']); - self::assertEquals('DeleteCollection request has failed', $record['message']); - self::assertArrayHasKey('exception', $record['context']); - self::assertInstanceOf(RuntimeException::class, $record['context']['exception']); - self::assertSame('Pod delete failed', $record['context']['exception']->getMessage()); - } - - public function testCheckResourceExists(): void - { - $podsApiClient = $this->createMock(PodsApiClient::class); - $podsApiClient->expects(self::once()) - ->method('get') - ->with('pod-name') - ->willReturn($this->createMock(Pod::class)) - ; - - $secretsApiClient = $this->createMock(SecretsApiClient::class); - $secretsApiClient->expects(self::once()) - ->method('get') - ->with('secret-name') - ->willThrowException(new ResourceNotFoundException('Secret not found', null)) - ; - - $facade = new KubernetesApiClientFacade( - $this->logger, - $this->createMock(ConfigMapsApiClient::class), - $this->createMock(EventsApiClient::class), - $this->createMock(IngressesApiClient::class), - $this->createMock(PersistentVolumeClaimsApiClient::class), - $this->createMock(PersistentVolumesApiClient::class), - $podsApiClient, - $secretsApiClient, - $this->createMock(ServicesApiClient::class), - ); - - self::assertFalse($facade->checkResourceExists(Secret::class, 'secret-name')); - self::assertTrue($facade->checkResourceExists(Pod::class, 'pod-name')); - } +// public function testCreateModels(): void +// { +// // request & result represent the same resource but are different class instances +// $podRequest1 = new Pod(['metadata' => ['name' => 'pod1']]); +// $podRequest2 = new Pod(['metadata' => ['name' => 'pod2']]); +// $secretRequest3 = new Secret(['metadata' => ['name' => 'secret3']]); +// $eventRequest4 = new Event(['metadata' => ['name' => 'event4']]); +// $serviceRequest5 = new Service(['metadata' => ['name' => 'service5']]); +// $ingressRequest6 = new Ingress(['metadata' => ['name' => 'ingress6']]); +// $persistentVolumeRequest7 = new PersistentVolume(['metadata' => ['name' => 'persistentVolume7']]); +// +// $podResult1 = new Pod(['metadata' => ['name' => 'pod1']]); +// $podResult2 = new Pod(['metadata' => ['name' => 'pod2']]); +// $secretResult3 = new Secret(['metadata' => ['name' => 'secret3']]); +// $eventResult4 = new Event(['metadata' => ['name' => 'event4']]); +// $serviceResult5 = new Service(['metadata' => ['name' => 'service5']]); +// $ingressResult6 = new Ingress(['metadata' => ['name' => 'ingress6']]); +// $persistentVolumeResult7 = new PersistentVolume(['metadata' => ['name' => 'persistentVolume7']]); +// +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::exactly(2)) +// ->method('create') +// ->withConsecutive( +// [$podRequest1, []], +// [$podRequest2, []], +// ) +// ->willReturnOnConsecutiveCalls( +// $podResult1, +// $podResult2, +// $secretResult3, +// ) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::once()) +// ->method('create') +// ->with($secretRequest3, []) +// ->willReturn($secretResult3) +// ; +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::once()) +// ->method('create') +// ->with($eventRequest4, []) +// ->willReturn($eventResult4) +// ; +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::once()) +// ->method('create') +// ->with($serviceRequest5, []) +// ->willReturn($serviceResult5) +// ; +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::once()) +// ->method('create') +// ->with($ingressRequest6, []) +// ->willReturn($ingressResult6) +// ; +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::once()) +// ->method('create') +// ->with($persistentVolumeRequest7, []) +// ->willReturn($persistentVolumeResult7) +// ; +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $this->createMock(ConfigMapsApiClient::class), +// $eventsApiClient, +// $ingressesApiClient, +// $this->createMock(PersistentVolumeClaimsApiClient::class), +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $result = $facade->createModels([ +// $podRequest1, +// $podRequest2, +// $secretRequest3, +// $eventRequest4, +// $serviceRequest5, +// $ingressRequest6, +// $persistentVolumeRequest7, +// ]); +// +// self::assertSame([ +// $podResult1, +// $podResult2, +// $secretResult3, +// $eventResult4, +// $serviceResult5, +// $ingressResult6, +// $persistentVolumeResult7, +// ], $result); +// } +// +// public function testCreateModelsErrorHandling(): void +// { +// $pod1 = new Pod(['metadata' => ['name' => 'pod1']]); +// $pod2 = new Pod(['metadata' => ['name' => 'pod2']]); +// $pod3 = new Pod(['metadata' => ['name' => 'pod3']]); +// +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::exactly(2)) +// ->method('create') +// ->withConsecutive( +// [$pod1, []], +// [$pod2, []], +// ) +// ->will(self::onConsecutiveCalls( +// self::returnArgument(0), +// self::throwException(new RuntimeException('Can\'t create Pod')), +// )) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::never())->method(self::anything()); +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::never())->method(self::anything()); +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::never())->method(self::anything()); +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::never())->method(self::anything()); +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::never())->method(self::anything()); +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $this->createMock(ConfigMapsApiClient::class), +// $eventsApiClient, +// $ingressesApiClient, +// $this->createMock(PersistentVolumeClaimsApiClient::class), +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $this->expectException(RuntimeException::class); +// $this->expectExceptionMessage('Can\'t create Pod'); +// +// $facade->createModels([$pod1, $pod2, $pod3]); +// } +// +// public function testDeleteModels(): void +// { +// // request & result represent the same resource but are different class instances +// $podRequest1 = new Pod(['metadata' => ['name' => 'pod1']]); +// $podRequest2 = new Pod(['metadata' => ['name' => 'pod2']]); +// $secretRequest3 = new Secret(['metadata' => ['name' => 'secret3']]); +// $eventRequest4 = new Event(['metadata' => ['name' => 'event4']]); +// $serviceRequest5 = new Service(['metadata' => ['name' => 'service5']]); +// $ingressRequest6 = new Ingress(['metadata' => ['name' => 'ingress6']]); +// $persistentVolumeRequest7 = new PersistentVolume(['metadata' => ['name' => 'persistentVolume7']]); +// +// $podResult1 = new Status(['metadata' => ['name' => 'pod1']]); +// $podResult2 = new Status(['metadata' => ['name' => 'pod2']]); +// $secretResult3 = new Status(['metadata' => ['name' => 'secret3']]); +// $eventResult4 = new Status(['metadata' => ['name' => 'event4']]); +// $serviceResult5 = new Status(['metadata' => ['name' => 'service5']]); +// $ingressResult6 = new Status(['metadata' => ['name' => 'ingress6']]); +// $persistentVolumeResult7 = new Status(['metadata' => ['name' => 'persistentVolume7']]); +// +// $deleteOptions = new DeleteOptions(); +// +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::exactly(2)) +// ->method('delete') +// ->withConsecutive( +// ['pod1', $deleteOptions, []], +// ['pod2', $deleteOptions, []], +// ) +// ->willReturnOnConsecutiveCalls( +// $podResult1, +// $podResult2, +// ) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::once()) +// ->method('delete') +// ->with('secret3', $deleteOptions, []) +// ->willReturn($secretResult3) +// ; +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::once()) +// ->method('delete') +// ->with('event4', $deleteOptions, []) +// ->willReturn($eventResult4) +// ; +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::once()) +// ->method('delete') +// ->with('service5', $deleteOptions, []) +// ->willReturn($serviceResult5) +// ; +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::once()) +// ->method('delete') +// ->with('ingress6', $deleteOptions, []) +// ->willReturn($ingressResult6) +// ; +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::once()) +// ->method('delete') +// ->with('persistentVolume7', $deleteOptions, []) +// ->willReturn($persistentVolumeResult7) +// ; +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $this->createMock(ConfigMapsApiClient::class), +// $eventsApiClient, +// $ingressesApiClient, +// $this->createMock(PersistentVolumeClaimsApiClient::class), +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $result = $facade->deleteModels([ +// $podRequest1, +// $podRequest2, +// $secretRequest3, +// $eventRequest4, +// $serviceRequest5, +// $ingressRequest6, +// $persistentVolumeRequest7, +// ], $deleteOptions); +// +// self::assertSame([ +// $podResult1, +// $podResult2, +// $secretResult3, +// $eventResult4, +// $serviceResult5, +// $ingressResult6, +// $persistentVolumeResult7, +// ], $result); +// } +// +// public function testDeleteModelsErrorHandling(): void +// { +// $pod1 = new Pod(['metadata' => ['name' => 'pod1']]); +// $pod2 = new Pod(['metadata' => ['name' => 'pod2']]); +// $pod3 = new Pod(['metadata' => ['name' => 'pod3']]); +// +// $deleteOptions = new DeleteOptions(); +// +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::exactly(2)) +// ->method('delete') +// ->withConsecutive( +// ['pod1', $deleteOptions, []], +// ['pod2', $deleteOptions, []], +// ) +// ->will(self::onConsecutiveCalls( +// new Status(['metadata' => ['name' => 'pod1']]), +// self::throwException(new RuntimeException('Can\'t delete Pod')), +// )) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::never())->method(self::anything()); +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::never())->method(self::anything()); +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::never())->method(self::anything()); +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::never())->method(self::anything()); +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::never())->method(self::anything()); +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $this->createMock(ConfigMapsApiClient::class), +// $eventsApiClient, +// $ingressesApiClient, +// $this->createMock(PersistentVolumeClaimsApiClient::class), +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $this->expectException(RuntimeException::class); +// $this->expectExceptionMessage('Can\'t delete Pod'); +// +// $facade->deleteModels([$pod1, $pod2, $pod3], $deleteOptions); +// } +// +// public function testWaitWhileExists(): void +// { +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::exactly(4)) +// ->method('get') +// ->withConsecutive( +// // first round check both pods, pod1 still exists, pod2 does not exist +// ['pod1'], +// ['pod2'], +// // second round checks remaining pod1 +// ['pod1'], +// // third round checks remaining pod1 +// ['pod1'], +// ) +// ->will(self::onConsecutiveCalls( +// new Pod(['metadata' => ['name' => 'pod1']]), +// self::throwException(new ResourceNotFoundException('Pod doesn\'t exist', null)), +// new Pod(['metadata' => ['name' => 'pod1']]), +// self::throwException(new ResourceNotFoundException('Pod doesn\'t exist', null)), +// )) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::never())->method(self::anything()); +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::never())->method(self::anything()); +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::never())->method(self::anything()); +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::never())->method(self::anything()); +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::never())->method(self::anything()); +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $this->createMock(ConfigMapsApiClient::class), +// $eventsApiClient, +// $ingressesApiClient, +// $this->createMock(PersistentVolumeClaimsApiClient::class), +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $facade->waitWhileExists([ +// new Pod(['metadata' => ['name' => 'pod1']]), +// new Pod(['metadata' => ['name' => 'pod2']]), +// ]); +// } +// +// public function testWaitWhileExistsTimeout(): void +// { +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient +// ->method('get') +// ->willReturnCallback(fn($podName) => new Pod(['metadata' => ['name' => $podName]])) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::never())->method(self::anything()); +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::never())->method(self::anything()); +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::never())->method(self::anything()); +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::never())->method(self::anything()); +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::never())->method(self::anything()); +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $this->createMock(ConfigMapsApiClient::class), +// $eventsApiClient, +// $ingressesApiClient, +// $this->createMock(PersistentVolumeClaimsApiClient::class), +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $startTime = microtime(true); +// try { +// $facade->waitWhileExists([ +// new Pod(['metadata' => ['name' => 'pod1']]), +// new Pod(['metadata' => ['name' => 'pod2']]), +// ], 3); +// self::fail('Expected TimeoutException was not thrown'); +// } catch (TimeoutException) { +// } +// $endTime = microtime(true); +// +// self::assertEqualsWithDelta(3, $endTime - $startTime, 1); +// } +// +// public function testListMatching(): void +// { +// $pod1 = new Pod(['metadata' => ['name' => 'pod-1']]); +// $pod2 = new Pod(['metadata' => ['name' => 'pod-2']]); +// $pod3 = new Pod(['metadata' => ['name' => 'pod-3']]); +// +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::exactly(2)) +// ->method('list') +// ->withConsecutive( +// [['labelSelector' => 'app=my', 'limit' => 100]], +// [['labelSelector' => 'app=my', 'limit' => 100, 'continue' => 'foo']], +// ) +// ->willReturnOnConsecutiveCalls( +// new PodList([ +// 'metadata' => [ +// 'continue' => 'foo', +// ], +// 'items' => [$pod1, $pod2], +// ]), +// new PodList([ +// 'items' => [$pod3], +// ]), +// ) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::never())->method(self::anything()); +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::never())->method(self::anything()); +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::never())->method(self::anything()); +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::never())->method(self::anything()); +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::never())->method(self::anything()); +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $this->createMock(ConfigMapsApiClient::class), +// $eventsApiClient, +// $ingressesApiClient, +// $this->createMock(PersistentVolumeClaimsApiClient::class), +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $result = $facade->listMatching(Pod::class, ['labelSelector' => 'app=my']); +// self::assertEquals([$pod1, $pod2, $pod3], [...$result]); +// } +// +// public function testListMatchingWithCustomPageSize(): void +// { +// $pod1 = new Pod(['metadata' => ['name' => 'pod-1']]); +// $pod2 = new Pod(['metadata' => ['name' => 'pod-2']]); +// $pod3 = new Pod(['metadata' => ['name' => 'pod-3']]); +// +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::exactly(2)) +// ->method('list') +// ->withConsecutive( +// [['labelSelector' => 'app=my', 'limit' => 5]], +// [['labelSelector' => 'app=my', 'limit' => 5, 'continue' => 'foo']], +// ) +// ->willReturnOnConsecutiveCalls( +// new PodList([ +// 'metadata' => [ +// 'continue' => 'foo', +// ], +// 'items' => [$pod1, $pod2], +// ]), +// new PodList([ +// 'items' => [$pod3], +// ]), +// ) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::never())->method(self::anything()); +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::never())->method(self::anything()); +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::never())->method(self::anything()); +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::never())->method(self::anything()); +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::never())->method(self::anything()); +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $this->createMock(ConfigMapsApiClient::class), +// $eventsApiClient, +// $ingressesApiClient, +// $this->createMock(PersistentVolumeClaimsApiClient::class), +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $result = $facade->listMatching(Pod::class, ['labelSelector' => 'app=my', 'limit' => 5]); +// self::assertEquals([$pod1, $pod2, $pod3], [...$result]); +// } +// +// public function testDeleteAllMatching(): void +// { +// $deleteOptions = new DeleteOptions(); +// $deleteQuery = ['labelSelector' => 'app=my-app']; +// +// $configMapsApiClient = $this->createMock(ConfigMapsApiClient::class); +// $configMapsApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $pvClaimApiClient = $this->createMock(PersistentVolumeClaimsApiClient::class); +// $pvClaimApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::never())->method(self::anything()); +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $configMapsApiClient, +// $eventsApiClient, +// $ingressesApiClient, +// $pvClaimApiClient, +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $facade->deleteAllMatching($deleteOptions, $deleteQuery); +// } +// +// public function testDeleteAllMatchingWithResourceTypesFilter(): void +// { +// $deleteOptions = new DeleteOptions(); +// $deleteQuery = ['labelSelector' => 'app=my-app']; +// +// $configMapsApiClient = $this->createMock(ConfigMapsApiClient::class); +// $configMapsApiClient->expects(self::never())->method(self::anything()); +// +// $pvClaimApiClient = $this->createMock(PersistentVolumeClaimsApiClient::class); +// $pvClaimApiClient->expects(self::never())->method(self::anything()); +// +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::never())->method(self::anything()); +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::never())->method(self::anything()); +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::never())->method(self::anything()); +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::never())->method(self::anything()); +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::never())->method(self::anything()); +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $configMapsApiClient, +// $eventsApiClient, +// $ingressesApiClient, +// $pvClaimApiClient, +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// $facade->deleteAllMatching($deleteOptions, ['resourceTypes' => [Secret::class], ...$deleteQuery]); +// } +// +// public function testDeleteAllMatchingErrorHandling(): void +// { +// $deleteOptions = new DeleteOptions(); +// $deleteQuery = ['labelSelector' => 'app=my-app']; +// +// $configMapsApiClient = $this->createMock(ConfigMapsApiClient::class); +// $configMapsApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->willThrowException(new RuntimeException('Config map delete failed')) +// ; +// +// $pvClaimApiClient = $this->createMock(PersistentVolumeClaimsApiClient::class); +// $pvClaimApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ->willThrowException(new RuntimeException('Pod delete failed')) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $eventsApiClient = $this->createMock(EventsApiClient::class); +// $eventsApiClient->expects(self::never())->method(self::anything()); +// +// $servicesApiClient = $this->createMock(ServicesApiClient::class); +// $servicesApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $ingressesApiClient = $this->createMock(IngressesApiClient::class); +// $ingressesApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $persistentVolumesApiClient = $this->createMock(PersistentVolumesApiClient::class); +// $persistentVolumesApiClient->expects(self::once()) +// ->method('deleteCollection') +// ->with($deleteOptions, $deleteQuery) +// ; +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $configMapsApiClient, +// $eventsApiClient, +// $ingressesApiClient, +// $pvClaimApiClient, +// $persistentVolumesApiClient, +// $podsApiClient, +// $secretsApiClient, +// $servicesApiClient, +// ); +// +// try { +// $facade->deleteAllMatching($deleteOptions, $deleteQuery); +// $this->fail('RuntimeException should be thrown on deleteAllMatching call.'); +// } catch (RuntimeException $e) { +// self::assertEquals('Pod delete failed', $e->getMessage()); +// } +// +// $records = $this->loggerTestHandler->getRecords(); +// self::assertCount(2, $records); +// +// $record = $records[0]; +// self::assertEquals(400, $record['level']); +// self::assertEquals('DeleteCollection request has failed', $record['message']); +// self::assertArrayHasKey('exception', $record['context']); +// self::assertInstanceOf(RuntimeException::class, $record['context']['exception']); +// self::assertSame('Config map delete failed', $record['context']['exception']->getMessage()); +// +// $record = $records[1]; +// self::assertEquals(400, $record['level']); +// self::assertEquals('DeleteCollection request has failed', $record['message']); +// self::assertArrayHasKey('exception', $record['context']); +// self::assertInstanceOf(RuntimeException::class, $record['context']['exception']); +// self::assertSame('Pod delete failed', $record['context']['exception']->getMessage()); +// } +// +// public function testCheckResourceExists(): void +// { +// $podsApiClient = $this->createMock(PodsApiClient::class); +// $podsApiClient->expects(self::once()) +// ->method('get') +// ->with('pod-name') +// ->willReturn($this->createMock(Pod::class)) +// ; +// +// $secretsApiClient = $this->createMock(SecretsApiClient::class); +// $secretsApiClient->expects(self::once()) +// ->method('get') +// ->with('secret-name') +// ->willThrowException(new ResourceNotFoundException('Secret not found', null)) +// ; +// +// $facade = new KubernetesApiClientFacade( +// $this->logger, +// $this->createMock(ConfigMapsApiClient::class), +// $this->createMock(EventsApiClient::class), +// $this->createMock(IngressesApiClient::class), +// $this->createMock(PersistentVolumeClaimsApiClient::class), +// $this->createMock(PersistentVolumesApiClient::class), +// $podsApiClient, +// $secretsApiClient, +// $this->createMock(ServicesApiClient::class), +// ); +// +// self::assertFalse($facade->checkResourceExists(Secret::class, 'secret-name')); +// self::assertTrue($facade->checkResourceExists(Pod::class, 'pod-name')); +// } } From 4b5a6cf439cff0c01540a0831a8393c064d77f0b Mon Sep 17 00:00:00 2001 From: Pepa Martinec Date: Tue, 9 Apr 2024 09:22:22 +0200 Subject: [PATCH 2/2] WIP Support for `replace` method on APIs --- libs/k8s-client/src/ApiClient/BaseNamespaceApiClient.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libs/k8s-client/src/ApiClient/BaseNamespaceApiClient.php b/libs/k8s-client/src/ApiClient/BaseNamespaceApiClient.php index ba1815f79..34d412623 100644 --- a/libs/k8s-client/src/ApiClient/BaseNamespaceApiClient.php +++ b/libs/k8s-client/src/ApiClient/BaseNamespaceApiClient.php @@ -65,6 +65,15 @@ public function patch(string $name, Patch $model, array $queries = []): Abstract return $this->apiClient->request($this->baseApi, 'patch', $this->itemClass, $name, $model, $queries); } + /** + * @param TItem $model + * @return TItem + */ + public function replace(string $name, AbstractModel $model, array $queries = []): AbstractModel + { + return $this->apiClient->request($this->baseApi, 'replace', $this->itemClass, $name, $model, $queries); + } + public function delete(string $name, ?DeleteOptions $options = null, array $queries = []): Status { $options ??= new DeleteOptions();