├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.xml ├── composer.json ├── phpunit.xml ├── src ├── Client.php ├── Collections │ ├── CertificateCollection.php │ ├── Collection.php │ ├── ConfigMapCollection.php │ ├── CronJobCollection.php │ ├── DaemonSetCollection.php │ ├── DeploymentCollection.php │ ├── EndpointCollection.php │ ├── EventCollection.php │ ├── HorizontalPodAutoscalerCollection.php │ ├── IngressCollection.php │ ├── IssuerCollection.php │ ├── JobCollection.php │ ├── NamespaceCollection.php │ ├── NetworkPolicyCollection.php │ ├── NodeCollection.php │ ├── PersistentVolumeClaimCollection.php │ ├── PersistentVolumeCollection.php │ ├── PodCollection.php │ ├── QuotaCollection.php │ ├── ReplicaSetCollection.php │ ├── ReplicationControllerCollection.php │ ├── RoleBindingCollection.php │ ├── RoleCollection.php │ ├── SecretCollection.php │ ├── ServiceAccountCollection.php │ └── ServiceCollection.php ├── Exceptions │ ├── ApiServerException.php │ ├── BadRequestException.php │ └── MissingOptionException.php ├── Models │ ├── Certificate.php │ ├── ConfigMap.php │ ├── CronJob.php │ ├── DaemonSet.php │ ├── DeleteOptions.php │ ├── Deployment.php │ ├── Endpoint.php │ ├── Event.php │ ├── HorizontalPodAutoscaler.php │ ├── Ingress.php │ ├── Issuer.php │ ├── Job.php │ ├── Model.php │ ├── NamespaceModel.php │ ├── NetworkPolicy.php │ ├── Node.php │ ├── PersistentVolume.php │ ├── PersistentVolumeClaim.php │ ├── Pod.php │ ├── QuotaModel.php │ ├── ReplicaSet.php │ ├── ReplicationController.php │ ├── Role.php │ ├── RoleBinding.php │ ├── Secret.php │ ├── Service.php │ └── ServiceAccount.php ├── Repositories │ ├── CertificateRepository.php │ ├── ConfigMapRepository.php │ ├── CronJobRepository.php │ ├── DaemonSetRepository.php │ ├── DeploymentRepository.php │ ├── EndpointRepository.php │ ├── EventRepository.php │ ├── HorizontalPodAutoscalerRepository.php │ ├── IngressRepository.php │ ├── IssuerRepository.php │ ├── JobRepository.php │ ├── NamespaceRepository.php │ ├── NetworkPolicyRepository.php │ ├── NodeRepository.php │ ├── PersistentVolumeClaimRepository.php │ ├── PersistentVolumeRepository.php │ ├── PodRepository.php │ ├── QuotaRepository.php │ ├── ReplicaSetRepository.php │ ├── ReplicationControllerRepository.php │ ├── Repository.php │ ├── RoleBindingRepository.php │ ├── RoleRepository.php │ ├── SecretRepository.php │ ├── ServiceAccountRepository.php │ ├── ServiceRepository.php │ ├── Strategy │ │ └── PatchMergeTrait.php │ └── Utils │ │ ├── JSONStreamingListener.php │ │ ├── JSONStreamingParser.php │ │ └── JSONStreamingParserHelper.php └── RepositoryRegistry.php └── tests ├── ClientTest.php ├── RepositoryRegistryTest.php ├── TestCase.php ├── collections ├── CertificateCollectionTest.php ├── ConfigMapCollectionTest.php ├── DeploymentCollectionTest.php ├── EndpointCollectionTest.php ├── EventCollectionTest.php ├── IngressCollectionTest.php ├── IssuerCollectionTest.php ├── JobCollectionTest.php ├── NamespaceCollectionTest.php ├── NetworkPolicyCollectionTest.php ├── NodeCollectionTest.php ├── PodCollectionTest.php ├── ReplicaSetCollectionTest.php ├── ReplicationControllerCollectionTest.php ├── RoleBindingCollectionTest.php ├── RoleCollectionTest.php ├── SecretCollectionTest.php ├── ServiceAccountCollectionTest.php └── ServiceCollectionTest.php ├── fixtures ├── certificates │ └── empty.json ├── config-maps │ └── empty.json ├── cron-jobs │ └── empty.json ├── deployments │ └── empty.json ├── endpoints │ └── empty.json ├── events │ └── empty.json ├── ingresses │ └── empty.json ├── issuers │ └── empty.json ├── jobs │ └── empty.json ├── namespaces │ └── empty.json ├── network-policies │ └── empty.json ├── nodes │ └── empty.json ├── pods │ └── empty.json ├── replica-sets │ └── empty.json ├── replication-controllers │ └── empty.json ├── roles-bindings │ └── empty.json ├── roles │ └── empty.json ├── secrets │ └── empty.json ├── services-accounts │ └── empty.json └── services │ └── empty.json ├── helpers └── HttpMethodsMockClient.php └── models ├── CertificateTest.php ├── ConfigMapTest.php ├── CronJobTest.php ├── DeploymentTest.php ├── EndpointTest.php ├── EventTest.php ├── IngressTest.php ├── IssuerTest.php ├── JobTest.php ├── ModelTest.php ├── NamespaceTest.php ├── NetworkPolicyTest.php ├── NodeTest.php ├── PodTest.php ├── ReplicaSetTest.php ├── ReplicationControllerTest.php ├── RoleBindingTest.php ├── RoleTest.php ├── SecretTest.php ├── ServiceAccountTest.php └── ServiceTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | # Real-Tab indents by default, width 4 8 | [*] 9 | end_of_line = lf 10 | insert_final_newline = true 11 | indent_style = tab 12 | indent_size = 4 13 | 14 | # Set default charset 15 | [*.{yml,php,xml,json}] 16 | charset = utf-8 17 | 18 | [*.yml] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /coverage 3 | /clover.xml 4 | /composer.lock 5 | /.idea 6 | /.settings/ 7 | /.buildpath 8 | /.project 9 | /build 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | dist: bionic 3 | 4 | php: 5 | - 7.4 6 | - 8.0 7 | #- 8.1 PHP8.1 is not available on travis's bionic distribution 8 | 9 | env: 10 | global: 11 | - XDEBUG_MODE=coverage 12 | 13 | before_script: 14 | - composer self-update 15 | - composer install --prefer-source --no-interaction 16 | 17 | script: vendor/bin/phpunit 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marc Lough 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Kubernetes Client 3 | 4 | [![Build Status](https://app.travis-ci.com/maclof/kubernetes-client.svg?branch=master)](https://app.travis-ci.com/maclof/kubernetes-client) 5 | 6 | A PHP client for managing a Kubernetes cluster. 7 | 8 | Last tested with v1.9.6 on a production cloud hosted cluster. 9 | 10 | 11 | ## Installation using [Composer](http://getcomposer.org/) 12 | 13 | ```bash 14 | $ composer require maclof/kubernetes-client 15 | ``` 16 | 17 | ## Supported API Features 18 | ### v1 19 | * Nodes 20 | * Namespaces 21 | * Pods 22 | * Replica Sets 23 | * Replication Controllers 24 | * Services 25 | * Secrets 26 | * Events 27 | * Config Maps 28 | * Endpoints 29 | * Persistent Volume 30 | * Persistent Volume Claims 31 | 32 | ### batch/v1 33 | * Jobs 34 | 35 | ### batch/v1beta1 36 | * Cron Jobs 37 | 38 | ### apps/v1 39 | * Deployments 40 | 41 | ### extensions/v1beta1 42 | * Daemon Sets 43 | 44 | ### networking.k8s.io/v1 45 | * Network Policies 46 | 47 | ### networking.k8s.io/v1beta1 48 | * Ingresses 49 | 50 | ### certmanager.k8s.io/v1alpha1 51 | * Certificates 52 | * Issuers 53 | 54 | ## Basic Usage 55 | 56 | ```php 57 | 'http://master.mycluster.com', 65 | ]); 66 | 67 | // Find pods by label selector 68 | $pods = $client->pods()->setLabelSelector([ 69 | 'name' => 'test', 70 | 'version' => 'a', 71 | ])->find(); 72 | 73 | // Both setLabelSelector and setFieldSelector can take an optional 74 | // second parameter which lets you define inequality based selectors (ie using the != operator) 75 | $pods = $client->pods()->setLabelSelector([ 76 | 'name' => 'test'], 77 | ['env' => 'staging'] 78 | ])->find(); 79 | 80 | // Find pods by field selector 81 | $pods = $client->pods()->setFieldSelector([ 82 | 'metadata.name' => 'test', 83 | ])->find(); 84 | 85 | // Find first pod with label selector (same for field selector) 86 | $pod = $client->pods()->setLabelSelector([ 87 | 'name' => 'test', 88 | ])->first(); 89 | ``` 90 | 91 | ### Using [JSONPath](https://github.com/FlowCommunications/JSONPath) 92 | It allows you to query status data. 93 | 94 | ```php 95 | $jobStartTime = $client->jobs()->find()->getJsonPath('$.status.startTime')[0]; 96 | ``` 97 | 98 | ## Authentication Examples 99 | 100 | ### Insecure HTTP 101 | ```php 102 | use Maclof\Kubernetes\Client; 103 | $client = new Client([ 104 | 'master' => 'http://master.mycluster.com', 105 | ]); 106 | ``` 107 | 108 | ### Secure HTTPS (CA + Client Certificate Validation) 109 | ```php 110 | use Maclof\Kubernetes\Client; 111 | use Http\Adapter\Guzzle6\Client as Guzzle6Client; 112 | $httpClient = Guzzle6Client::createWithConfig([ 113 | 'verify' => '/etc/kubernetes/ssl/ca.crt', 114 | 'cert' => '/etc/kubernetes/ssl/client.crt', 115 | 'ssl_key' => '/etc/kubernetes/ssl/client.key', 116 | ]); 117 | $client = new Client([ 118 | 'master' => 'https://master.mycluster.com', 119 | ], null, $httpClient); 120 | ``` 121 | 122 | ### Insecure HTTPS (CA Certificate Verification Disabled) 123 | ```php 124 | use Maclof\Kubernetes\Client; 125 | use Http\Adapter\Guzzle6\Client as Guzzle6Client; 126 | $httpClient = Guzzle6Client::createWithConfig([ 127 | 'verify' => false, 128 | ]); 129 | $client = new Client([ 130 | 'master' => 'https://master.mycluster.com', 131 | ], null, $httpClient); 132 | ``` 133 | 134 | ### Using Basic Auth 135 | ```php 136 | use Maclof\Kubernetes\Client; 137 | $client = new Client([ 138 | 'master' => 'https://master.mycluster.com', 139 | 'username' => 'admin', 140 | 'password' => 'abc123', 141 | ]); 142 | ``` 143 | 144 | ### Using a Service Account 145 | ```php 146 | use Maclof\Kubernetes\Client; 147 | use Http\Adapter\Guzzle6\Client as Guzzle6Client; 148 | $httpClient = Guzzle6Client::createWithConfig([ 149 | 'verify' => '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt', 150 | ]); 151 | $client = new Client([ 152 | 'master' => 'https://master.mycluster.com', 153 | 'token' => '/var/run/secrets/kubernetes.io/serviceaccount/token', 154 | ], null, $httpClient); 155 | ``` 156 | 157 | ### Parsing a kubeconfig file 158 | ```php 159 | use Maclof\Kubernetes\Client; 160 | 161 | // Parsing from the file data directly 162 | $config = Client::parseKubeConfig('kubeconfig yaml data'); 163 | 164 | // Parsing from the file path 165 | $config = Client::parseKubeConfigFile('~/.kube/config.yml'); 166 | 167 | // Example config that may be returned 168 | // You would then feed these options into the http/kubernetes client constructors. 169 | $config = [ 170 | 'master' => 'https://master.mycluster.com', 171 | 'ca_cert' => '/temp/path/ca.crt', 172 | 'client_cert' => '/temp/path/client.crt', 173 | 'client_key' => '/temp/path/client.key', 174 | ]; 175 | ``` 176 | 177 | ## Extending a library 178 | 179 | ### Custom repositories 180 | ```php 181 | use Maclof\Kubernetes\Client; 182 | 183 | $repositories = new RepositoryRegistry(); 184 | $repositories['things'] = MyApp\Kubernetes\Repository\ThingRepository::class; 185 | 186 | $client = new Client([ 187 | 'master' => 'https://master.mycluster.com', 188 | ], $repositories); 189 | 190 | $client->things(); //ThingRepository 191 | ``` 192 | 193 | ## Usage Examples 194 | 195 | ### Create/Update a Replication Controller 196 | The below example uses an array to specify the replication controller's attributes. You can specify the attributes either as an array, JSON encoded string or a YAML encoded string. The second parameter to the model constructor is the data type and defaults to array. 197 | ```php 198 | use Maclof\Kubernetes\Models\ReplicationController; 199 | 200 | $replicationController = new ReplicationController([ 201 | 'metadata' => [ 202 | 'name' => 'nginx-test', 203 | 'labels' => [ 204 | 'name' => 'nginx-test', 205 | ], 206 | ], 207 | 'spec' => [ 208 | 'replicas' => 1, 209 | 'template' => [ 210 | 'metadata' => [ 211 | 'labels' => [ 212 | 'name' => 'nginx-test', 213 | ], 214 | ], 215 | 'spec' => [ 216 | 'containers' => [ 217 | [ 218 | 'name' => 'nginx', 219 | 'image' => 'nginx', 220 | 'ports' => [ 221 | [ 222 | 'containerPort' => 80, 223 | 'protocol' => 'TCP', 224 | ], 225 | ], 226 | ], 227 | ], 228 | ], 229 | ], 230 | ], 231 | ]); 232 | 233 | if ($client->replicationControllers()->exists($replicationController->getMetadata('name'))) { 234 | $client->replicationControllers()->update($replicationController); 235 | } else { 236 | $client->replicationControllers()->create($replicationController); 237 | } 238 | ``` 239 | 240 | ### Delete a Replication Controller 241 | ```php 242 | $replicationController = $client->replicationControllers()->setLabelSelector([ 243 | 'name' => 'nginx-test', 244 | ])->first(); 245 | $client->replicationControllers()->delete($replicationController); 246 | ``` 247 | 248 | You can also specify options when performing a deletion, eg. to perform [cascading delete]( https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#setting-the-cascading-deletion-policy) 249 | 250 | ```php 251 | use Maclof\Kubernetes\Models\DeleteOptions; 252 | 253 | $client->replicationControllers()->delete( 254 | $replicationController, 255 | new DeleteOptions(['propagationPolicy' => 'Background']) 256 | ); 257 | ``` 258 | 259 | See the API documentation for an explanation of the options: 260 | 261 | https://kubernetes.io/docs/api-reference/v1.6/#deleteoptions-v1-meta 262 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maclof/kubernetes-client", 3 | "description": "A simple yet elegant client for accessing and controlling a Kubernetes cluster.", 4 | "keywords": ["kubernetes", "docker", "container", "cluster"], 5 | "authors": [ 6 | { 7 | "name" : "Marc Lough", 8 | "homepage": "http://maclof.com" 9 | } 10 | ], 11 | "license" : "MIT", 12 | "require": { 13 | "php" : "^7.4|^8.0", 14 | "php-http/client-common": "^2.0", 15 | "php-http/discovery" : "^1.0", 16 | "illuminate/support" : "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", 17 | "ratchet/pawl" : "^0.3", 18 | "symfony/yaml" : "^4.0|^5.0|^6.0|^7.0", 19 | "softcreatr/jsonpath" : "^0.5 || ^0.7 || ^0.8" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit" : "~7.0|~9.5", 23 | "mockery/mockery" : "~1.2", 24 | "php-coveralls/php-coveralls": "~2.0", 25 | "php-http/socket-client" : "^2.0", 26 | "nyholm/psr7": "^1.4" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Maclof\\Kubernetes\\": "src/" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "classmap": [ 35 | "tests/TestCase.php", 36 | "tests/helpers/HttpMethodsMockClient.php" 37 | ] 38 | }, 39 | "minimum-stability": "dev", 40 | "prefer-stable": true 41 | } 42 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | ./src 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | 'application/strategic-merge-patch+json']; 136 | 137 | protected ?bool $verify = null; 138 | 139 | protected ?string $caCert = null; 140 | 141 | protected ?string $clientCert = null; 142 | 143 | protected ?string $clientKey = null; 144 | 145 | /** 146 | * The constructor. 147 | */ 148 | public function __construct(array $options = [], RepositoryRegistry $repositoryRegistry = null, ClientInterface $httpClient = null, HttpRequestFactory $httpRequestFactory = null, StreamFactoryInterface $streamFactory = null) 149 | { 150 | $this->setOptions($options); 151 | $this->classRegistry = $repositoryRegistry ?: new RepositoryRegistry(); 152 | $this->httpClient = new HttpMethodsClient( 153 | $httpClient ?: Psr18ClientDiscovery::find(), 154 | $httpRequestFactory ?: Psr17FactoryDiscovery::findRequestFactory(), 155 | $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory() 156 | ); 157 | } 158 | 159 | /** 160 | * Set the options. 161 | */ 162 | public function setOptions(array $options, bool $reset = false): void 163 | { 164 | if ($reset) { 165 | $this->master = null; 166 | $this->verify = null; 167 | $this->caCert = null; 168 | $this->clientCert = null; 169 | $this->clientKey = null; 170 | $this->token = null; 171 | $this->username = null; 172 | $this->password = null; 173 | $this->namespace = 'default'; 174 | } 175 | 176 | if (isset($options['master'])) { 177 | $this->master = $options['master']; 178 | } 179 | if (isset($options['verify'])) { 180 | $this->verify = $options['verify']; 181 | } elseif (isset($options['ca_cert'])) { 182 | $this->caCert = $options['ca_cert']; 183 | } 184 | if (isset($options['client_cert'])) { 185 | $this->clientCert = $options['client_cert']; 186 | } 187 | if (isset($options['client_key'])) { 188 | $this->clientKey = $options['client_key']; 189 | } 190 | if (isset($options['token'])) { 191 | $this->token = $options['token']; 192 | } 193 | if (isset($options['username'])) { 194 | $this->username = $options['username']; 195 | } 196 | if (isset($options['password'])) { 197 | $this->password = $options['password']; 198 | } 199 | if (isset($options['namespace'])) { 200 | $this->namespace = $options['namespace']; 201 | } 202 | } 203 | 204 | /** 205 | * Parse a kubeconfig. 206 | * 207 | * @param string|array $content Mixed type, based on the second input argument 208 | * @throws \InvalidArgumentException 209 | */ 210 | public static function parseKubeconfig($content, string $contentType = 'yaml'): array 211 | { 212 | if ($contentType === 'array') { 213 | if (!is_array($content)) { 214 | throw new InvalidArgumentException('Kubeconfig is not an array.'); 215 | } 216 | } elseif ($contentType === 'json') { 217 | if (!is_string($content)) { 218 | throw new InvalidArgumentException('Kubeconfig is not a string.'); 219 | } 220 | 221 | if (defined('JSON_THROW_ON_ERROR')) { 222 | try { 223 | $content = json_decode($content, true, 512, JSON_THROW_ON_ERROR); 224 | } catch (\JsonException $e) { 225 | throw new InvalidArgumentException('Failed to parse JSON encoded Kubeconfig: ' . $e->getMessage(), 0, $e); 226 | } 227 | } else { 228 | $content = json_decode($content, true, 512); 229 | if ($content === false || $content === null) { 230 | throw new InvalidArgumentException('Failed to parse JSON encoded Kubeconfig.'); 231 | } 232 | } 233 | } elseif ($contentType === 'yaml') { 234 | if (!is_string($content)) { 235 | throw new InvalidArgumentException('Kubeconfig is not a string.'); 236 | } 237 | try { 238 | $content = Yaml::parse($content); 239 | } catch (YamlParseException $e) { 240 | throw new InvalidArgumentException('Failed to parse YAML encoded Kubeconfig: ' . $e->getMessage(), 0, $e); 241 | } 242 | } else { 243 | throw new InvalidArgumentException('Invalid Kubeconfig content type: ' . $contentType); 244 | } 245 | 246 | // TODO: support token auth? 247 | 248 | $contexts = []; 249 | if (isset($content['contexts']) && is_array($content['contexts'])) { 250 | foreach ($content['contexts'] as $context) { 251 | $contexts[$context['name']] = $context['context']; 252 | } 253 | } 254 | if (count($contexts) === 0) { 255 | throw new InvalidArgumentException('Kubeconfig parse error - No contexts are defined.'); 256 | } 257 | 258 | $clusters = []; 259 | if (isset($content['clusters']) && is_array($content['clusters'])) { 260 | foreach ($content['clusters'] as $cluster) { 261 | $clusters[$cluster['name']] = $cluster['cluster']; 262 | } 263 | } 264 | if (count($clusters) === 0) { 265 | throw new InvalidArgumentException('Kubeconfig parse error - No clusters are defined.'); 266 | } 267 | 268 | $users = []; 269 | if (isset($content['users']) && is_array($content['users'])) { 270 | foreach ($content['users'] as $user) { 271 | $users[$user['name']] = $user['user']; 272 | } 273 | } 274 | if (count($users) === 0) { 275 | throw new InvalidArgumentException('Kubeconfig parse error - No users are defined.'); 276 | } 277 | 278 | if (!isset($content['current-context'])) { 279 | throw new InvalidArgumentException('Kubeconfig parse error - Missing current context attribute.'); 280 | } 281 | if (!isset($contexts[$content['current-context']])) { 282 | throw new InvalidArgumentException('Kubeconfig parse error - The current context "' . $content['current-context'] . '" is undefined.'); 283 | } 284 | $context = $contexts[$content['current-context']]; 285 | 286 | if (!isset($context['cluster'])) { 287 | throw new InvalidArgumentException('Kubeconfig parse error - The current context is missing the cluster attribute.'); 288 | } 289 | if (!isset($clusters[$context['cluster']])) { 290 | throw new InvalidArgumentException('Kubeconfig parse error - The cluster "' . $context['cluster'] . '" is undefined.'); 291 | } 292 | $cluster = $clusters[$context['cluster']]; 293 | 294 | if (!isset($context['user'])) { 295 | throw new InvalidArgumentException('Kubeconfig parse error - The current context is missing the user attribute.'); 296 | } 297 | if (!isset($users[$context['user']])) { 298 | throw new InvalidArgumentException('Kubeconfig parse error - The user "' . $context['user'] . '" is undefined.'); 299 | } 300 | $user = $users[$context['user']]; 301 | 302 | $options = []; 303 | 304 | if (!isset($cluster['server'])) { 305 | throw new InvalidArgumentException('Kubeconfig parse error - The cluster "' . $context['cluster'] . '" is missing the server attribute.'); 306 | } 307 | $options['master'] = $cluster['server']; 308 | 309 | if (isset($cluster['certificate-authority-data'])) { 310 | $options['ca_cert'] = self::getTempFilePath('ca-cert.pem', base64_decode($cluster['certificate-authority-data'], true)); 311 | } elseif (strpos($options['master'], 'https://') !== false) { 312 | $options['verify'] = false; 313 | } 314 | 315 | if (isset($user['client-certificate-data'])) { 316 | $options['client_cert'] = self::getTempFilePath('client-cert.pem', base64_decode($user['client-certificate-data'], true)); 317 | } 318 | 319 | if (isset($user['client-key-data'])) { 320 | $options['client_key'] = self::getTempFilePath('client-key.pem', base64_decode($user['client-key-data'], true)); 321 | } 322 | 323 | return $options; 324 | } 325 | 326 | /** 327 | * Parse a kubeconfig file. 328 | * 329 | * @throws \InvalidArgumentException 330 | */ 331 | public static function parseKubeconfigFile(string $filePath): array 332 | { 333 | if (!file_exists($filePath)) { 334 | throw new InvalidArgumentException('Kubeconfig file does not exist at path: ' . $filePath); 335 | } 336 | 337 | return self::parseKubeconfig(file_get_contents($filePath)); 338 | } 339 | 340 | /** 341 | * Get a temp file path for some content. 342 | */ 343 | protected static function getTempFilePath(string $fileName, string $fileContent): string 344 | { 345 | $fileName = 'kubernetes-client-' . $fileName; 346 | 347 | $tempFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; 348 | 349 | if (file_put_contents($tempFilePath, $fileContent) === false) { 350 | throw new Exception('Failed to write content to temp file: ' . $tempFilePath); 351 | } 352 | 353 | return $tempFilePath; 354 | } 355 | 356 | /** 357 | * Set namespace. 358 | */ 359 | public function setNamespace(string $namespace): void 360 | { 361 | $this->namespace = $namespace; 362 | } 363 | 364 | /** 365 | * Set patch header 366 | */ 367 | public function setPatchType(string $patchType = "strategic"): void 368 | { 369 | if ($patchType === "merge") { 370 | $this->patchHeaders = ['Content-Type' => 'application/merge-patch+json']; 371 | } elseif ($patchType === "json") { 372 | $this->patchHeaders = ['Content-Type' => 'application/json-patch+json']; 373 | } else { 374 | $this->patchHeaders = ['Content-Type' => 'application/strategic-merge-patch+json']; 375 | } 376 | } 377 | 378 | /** 379 | * Send a request. 380 | * 381 | * @param mixed $body 382 | * @return mixed 383 | * @throws \Maclof\Kubernetes\Exceptions\BadRequestException 384 | */ 385 | #[\ReturnTypeWillChange] 386 | public function sendRequest(string $method, string $uri, array $query = [], $body = null, bool $namespace = true, string $apiVersion = null, array $requestOptions = []) 387 | { 388 | $baseUri = $apiVersion ? ('apis/' . $apiVersion) : ('api/' . $this->apiVersion); 389 | if ($namespace) { 390 | $baseUri .= '/namespaces/' . $this->namespace; 391 | } 392 | 393 | if ($uri === '/healthz' || $uri === '/version') { 394 | $requestUrl = $this->master . '/' . $uri; 395 | } else { 396 | $requestUrl = $this->master . '/' . $baseUri . $uri; 397 | } 398 | 399 | if (is_array($query) && !empty($query)) { 400 | $requestUrl .= '?' . http_build_query($query); 401 | } 402 | 403 | try { 404 | $headers = $method === 'PATCH' ? $this->patchHeaders : []; 405 | if ('POST' === $method) { 406 | $headers['Content-Type'] = 'application/json'; 407 | } 408 | 409 | if ($this->token) { 410 | $token = $this->token; 411 | if (file_exists($token)) { 412 | $token = file_get_contents($token); 413 | } 414 | $headers['Authorization'] = 'Bearer ' . trim($token); 415 | } 416 | 417 | if (!is_null($body)) { 418 | $body = is_array($body) ? json_encode($body, JSON_FORCE_OBJECT) : $body; 419 | } 420 | 421 | $response = $this->httpClient->send($method, $requestUrl, $headers, $body); 422 | 423 | // Error Handling 424 | if ($response->getStatusCode() >= 500) { 425 | $msg = substr((string) $response->getBody(), 0, 1200); // Limit maximum chars 426 | throw new ApiServerException("Server responded with 500 Error: " . $msg, 500); 427 | } 428 | 429 | if (in_array($response->getStatusCode(), [401, 403], true)) { 430 | $msg = substr((string) $response->getBody(), 0, 1200); // Limit maximum chars 431 | throw new ApiServerException("Authentication Exception: " . $msg, $response->getStatusCode()); 432 | } 433 | 434 | if (isset($requestOptions['stream']) && $requestOptions['stream'] === true) { 435 | return $response; 436 | } 437 | 438 | $responseBody = (string) $response->getBody(); 439 | $jsonResponse = json_decode($responseBody, true); 440 | 441 | if ($jsonResponse !== null && array_key_exists('code', $jsonResponse) && $this->isUpgradeRequestRequired($jsonResponse)) { 442 | return $this->sendUpgradeRequest($requestUrl, $query); 443 | } 444 | 445 | return is_array($jsonResponse) ? $jsonResponse : $responseBody; 446 | } catch (HttpTransferException $e) { 447 | $response = $e->getResponse(); 448 | 449 | $responseBody = (string) $response->getBody(); 450 | 451 | if (in_array('application/json', $response->getHeader('Content-Type'), true)) { 452 | $jsonResponse = json_decode($responseBody, true); 453 | 454 | if ($this->isUpgradeRequestRequired($jsonResponse)) { 455 | return $this->sendUpgradeRequest($requestUrl, $query); 456 | } 457 | } 458 | 459 | throw new BadRequestException($responseBody, 0, $e); 460 | } 461 | } 462 | 463 | /** 464 | * Check if an upgrade request is required. 465 | */ 466 | protected function isUpgradeRequestRequired(array $response): bool 467 | { 468 | return $response['code'] == 400 && $response['status'] === 'Failure' && $response['message'] === 'Upgrade request required'; 469 | } 470 | 471 | /** 472 | * Send an upgrade request and return any response messages. 473 | */ 474 | protected function sendUpgradeRequest(string $requestUri, array $query): array 475 | { 476 | $fullUrl = $requestUri . '?' . implode('&', $this->parseQueryParams($query)); 477 | if (parse_url($fullUrl, PHP_URL_SCHEME) === 'https') { 478 | $fullUrl = str_replace('https://', 'wss://', $fullUrl); 479 | } else { 480 | $fullUrl = str_replace('http://', 'ws://', $fullUrl); 481 | } 482 | 483 | $headers = []; 484 | $socketOptions = [ 485 | 'timeout' => 20, 486 | 'tls' => [], 487 | ]; 488 | 489 | if ($this->token) { 490 | $token = $this->token; 491 | if (file_exists($token)) { 492 | $token = file_get_contents($token); 493 | } 494 | 495 | $headers['Authorization'] = 'Bearer ' . trim($token); 496 | } elseif ($this->username && $this->password) { 497 | $headers['Authorization'] = 'Basic ' . base64_encode($this->username . ':' . $this->password); 498 | } 499 | 500 | if (!is_null($this->verify)) { 501 | $socketOptions['tls']['verify_peer'] = $this->verify; 502 | $socketOptions['tls']['verify_peer_name'] = $this->verify; 503 | } elseif ($this->caCert) { 504 | $socketOptions['tls']['cafile'] = $this->caCert; 505 | } 506 | 507 | if ($this->clientCert) { 508 | $socketOptions['tls']['local_cert'] = $this->clientCert; 509 | } 510 | if ($this->clientKey) { 511 | $socketOptions['tls']['local_pk'] = $this->clientKey; 512 | } 513 | 514 | $loop = ReactFactory::create(); 515 | 516 | $socketConnector = new ReactSocketConnector($loop, $socketOptions); 517 | 518 | $wsConnector = new WebSocketConnector($loop, $socketConnector); 519 | 520 | $connPromise = $wsConnector($fullUrl, ['base64.channel.k8s.io'], $headers); 521 | 522 | $wsConnection = null; 523 | $messages = []; 524 | 525 | $connPromise->then(function ($conn) use (&$wsConnection, &$messages) { 526 | $wsConnection = $conn; 527 | 528 | $conn->on('message', function ($message) use (&$messages) { 529 | $data = $message->getPayload(); 530 | 531 | $channel = $this->execChannels[substr($data, 0, 1)]; 532 | $message = base64_decode(substr($data, 1)); 533 | 534 | $messages[] = [ 535 | 'channel' => $channel, 536 | 'message' => $message, 537 | ]; 538 | }); 539 | }, function ($e) { 540 | throw new BadRequestException('Websocket Exception', 0, $e); 541 | }); 542 | 543 | $loop->run(); 544 | 545 | if ($wsConnection) { 546 | $wsConnection->close(); 547 | } 548 | 549 | return $messages; 550 | } 551 | 552 | /** 553 | * Parse an array of query params. 554 | */ 555 | protected function parseQueryParams(array $query): array 556 | { 557 | $parts = []; 558 | 559 | foreach ($query as $key => $value) { 560 | if (is_array($value)) { 561 | foreach ($value as $val) { 562 | $parts[] = $key . '=' . $val; 563 | } 564 | 565 | continue; 566 | } 567 | 568 | $parts[] = $key . '=' . $value; 569 | } 570 | 571 | return $parts; 572 | } 573 | 574 | /** 575 | * Check the health. 576 | * 577 | * @return array|string 578 | */ 579 | public function health() 580 | { 581 | return $this->sendRequest('GET', '/healthz'); 582 | } 583 | 584 | /** 585 | * Check the version. 586 | */ 587 | public function version(): array 588 | { 589 | return $this->sendRequest('GET', '/version'); 590 | } 591 | 592 | /** 593 | * Magic call method to grab a class instance. 594 | * 595 | * @return \stdClass 596 | * @throws \BadMethodCallException 597 | */ 598 | public function __call(string $name, array $args) 599 | { 600 | if (isset($this->classRegistry[$name])) { 601 | $class = $this->classRegistry[$name]; 602 | 603 | return $this->classInstances[$name] ?? new $class($this); 604 | } 605 | 606 | throw new BadMethodCallException('No client methods exist with the name: ' . $name); 607 | } 608 | } 609 | -------------------------------------------------------------------------------- /src/Collections/CertificateCollection.php: -------------------------------------------------------------------------------- 1 | getCertificates($items)); 13 | } 14 | 15 | /** 16 | * Get an array of certificates. 17 | */ 18 | protected function getCertificates(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Certificate) { 22 | continue; 23 | } 24 | 25 | $item = new Certificate($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/Collection.php: -------------------------------------------------------------------------------- 1 | getConfigMaps($items)); 13 | } 14 | 15 | /** 16 | * Get an array of config maps. 17 | */ 18 | protected function getConfigMaps(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof ConfigMap) { 22 | continue; 23 | } 24 | 25 | $item = new ConfigMap($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/CronJobCollection.php: -------------------------------------------------------------------------------- 1 | getCronJobs($items)); 13 | } 14 | 15 | /** 16 | * Get an array of cron jobs. 17 | */ 18 | protected function getCronJobs(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof CronJob) { 22 | continue; 23 | } 24 | 25 | $item = new CronJob($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/DaemonSetCollection.php: -------------------------------------------------------------------------------- 1 | getDaemonSets($items)); 13 | } 14 | 15 | /** 16 | * Get an array of daemon sets. 17 | */ 18 | protected function getDaemonSets(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof DaemonSet) { 22 | continue; 23 | } 24 | 25 | $item = new DaemonSet($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/DeploymentCollection.php: -------------------------------------------------------------------------------- 1 | getDeployments($items)); 13 | } 14 | 15 | /** 16 | * Get an array of deployments. 17 | */ 18 | protected function getDeployments(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Deployment) { 22 | continue; 23 | } 24 | 25 | $item = new Deployment($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/EndpointCollection.php: -------------------------------------------------------------------------------- 1 | getEndpoints($items)); 13 | } 14 | 15 | /** 16 | * Get an array of nodes. 17 | */ 18 | protected function getEndpoints(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Endpoint) { 22 | continue; 23 | } 24 | 25 | $item = new Endpoint($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/EventCollection.php: -------------------------------------------------------------------------------- 1 | getEvents($items)); 13 | } 14 | 15 | /** 16 | * Get an array of nodes. 17 | */ 18 | protected function getEvents(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Event) { 22 | continue; 23 | } 24 | 25 | $item = new Event($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/HorizontalPodAutoscalerCollection.php: -------------------------------------------------------------------------------- 1 | getHorizontalPodAutoscalers($items)); 13 | } 14 | 15 | /** 16 | * Get an array of autoscalers. 17 | */ 18 | protected function getHorizontalPodAutoscalers(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof HorizontalPodAutoscaler) { 22 | continue; 23 | } 24 | 25 | $item = new HorizontalPodAutoscaler($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/IngressCollection.php: -------------------------------------------------------------------------------- 1 | getIngresses($items)); 13 | } 14 | 15 | /** 16 | * Get an array of Ingresses. 17 | */ 18 | protected function getIngresses(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Ingress) { 22 | continue; 23 | } 24 | 25 | $item = new Ingress($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/IssuerCollection.php: -------------------------------------------------------------------------------- 1 | getIssuers($items)); 13 | } 14 | 15 | /** 16 | * Get an array of certificate issuers. 17 | */ 18 | protected function getIssuers(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Issuer) { 22 | continue; 23 | } 24 | 25 | $item = new Issuer($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/JobCollection.php: -------------------------------------------------------------------------------- 1 | getJobs($items)); 13 | } 14 | 15 | /** 16 | * Get an array of jobs. 17 | */ 18 | protected function getJobs(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Job) { 22 | continue; 23 | } 24 | 25 | $item = new Job($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/NamespaceCollection.php: -------------------------------------------------------------------------------- 1 | getNamespaces($items)); 13 | } 14 | 15 | /** 16 | * Get an array of Namespaces. 17 | */ 18 | protected function getNamespaces(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof NamespaceModel) { 22 | continue; 23 | } 24 | 25 | $item = new NamespaceModel($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/NetworkPolicyCollection.php: -------------------------------------------------------------------------------- 1 | getPolicies($items)); 13 | } 14 | 15 | /** 16 | * Get an array of network policies. 17 | */ 18 | protected function getPolicies(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof NetworkPolicy) { 22 | continue; 23 | } 24 | 25 | $item = new NetworkPolicy($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/NodeCollection.php: -------------------------------------------------------------------------------- 1 | getNodes($items)); 13 | } 14 | 15 | /** 16 | * Get an array of nodes. 17 | */ 18 | protected function getNodes(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Node) { 22 | continue; 23 | } 24 | 25 | $item = new Node($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/PersistentVolumeClaimCollection.php: -------------------------------------------------------------------------------- 1 | getPersistentVolumeClaims($items)); 13 | } 14 | 15 | /** 16 | * Get an array of persistent volume claims. 17 | */ 18 | protected function getPersistentVolumeClaims(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof PersistentVolumeClaim) { 22 | continue; 23 | } 24 | 25 | $item = new PersistentVolumeClaim($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/PersistentVolumeCollection.php: -------------------------------------------------------------------------------- 1 | getPersistentVolumes($items)); 14 | } 15 | 16 | /** 17 | * Get an array of persistent volumes. 18 | */ 19 | protected function getPersistentVolumes(array $items): array 20 | { 21 | foreach ($items as &$item) { 22 | if ($item instanceof PersistentVolume) { 23 | continue; 24 | } 25 | 26 | $item = new PersistentVolume($item); 27 | } 28 | 29 | return $items; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Collections/PodCollection.php: -------------------------------------------------------------------------------- 1 | getPods($items)); 13 | } 14 | 15 | /** 16 | * Get an array of pods. 17 | */ 18 | protected function getPods(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Pod) { 22 | continue; 23 | } 24 | 25 | $item = new Pod($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/QuotaCollection.php: -------------------------------------------------------------------------------- 1 | getQuotas($items)); 13 | } 14 | 15 | /** 16 | * Get an array of Namespaces. 17 | */ 18 | protected function getQuotas(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof QuotaModel) { 22 | continue; 23 | } 24 | 25 | $item = new QuotaModel($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/ReplicaSetCollection.php: -------------------------------------------------------------------------------- 1 | getReplicaSets($items)); 13 | } 14 | 15 | /** 16 | * Get an array of replication sets. 17 | */ 18 | protected function getReplicaSets(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof ReplicaSet) { 22 | continue; 23 | } 24 | 25 | $item = new ReplicaSet($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/ReplicationControllerCollection.php: -------------------------------------------------------------------------------- 1 | getReplicationControllers($items)); 13 | } 14 | 15 | /** 16 | * Get an array of replication controllers. 17 | */ 18 | protected function getReplicationControllers(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof ReplicationController) { 22 | continue; 23 | } 24 | 25 | $item = new ReplicationController($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/RoleBindingCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class RoleBindingCollection extends Collection 13 | { 14 | /** 15 | * @param array|RoleBinding> $items 16 | */ 17 | public function __construct(array $items) 18 | { 19 | parent::__construct($this->getServiceAccounts($items)); 20 | } 21 | 22 | /** 23 | * Get an array of serviceAccounts. 24 | * 25 | * @param array|RoleBinding> $items 26 | * @return array 27 | */ 28 | protected function getServiceAccounts(array $items) 29 | { 30 | $final = []; 31 | foreach ($items as &$item) { 32 | if (!$item instanceof RoleBinding) { 33 | $final[] = new RoleBinding($item); 34 | } else { 35 | $final[] = $item; 36 | } 37 | } 38 | 39 | return $final; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Collections/RoleCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class RoleCollection extends Collection 13 | { 14 | /** 15 | * @param array|Role> $items 16 | */ 17 | public function __construct(array $items) 18 | { 19 | parent::__construct($this->getServiceAccounts($items)); 20 | } 21 | 22 | /** 23 | * Get an array of serviceAccounts. 24 | * 25 | * @param array|Role> $items 26 | * @return array 27 | */ 28 | protected function getServiceAccounts(array $items) 29 | { 30 | $final = []; 31 | foreach ($items as &$item) { 32 | if (!$item instanceof Role) { 33 | $final[] = new Role($item); 34 | } else { 35 | $final[] = $item; 36 | } 37 | } 38 | 39 | return $final; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Collections/SecretCollection.php: -------------------------------------------------------------------------------- 1 | getSecrets($items)); 13 | } 14 | 15 | /** 16 | * Get an array of secrets. 17 | */ 18 | protected function getSecrets(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Secret) { 22 | continue; 23 | } 24 | 25 | $item = new Secret($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Collections/ServiceAccountCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ServiceAccountCollection extends Collection 13 | { 14 | /** 15 | * @param array|ServiceAccount> $items 16 | */ 17 | public function __construct(array $items) 18 | { 19 | parent::__construct($this->getServiceAccounts($items)); 20 | } 21 | 22 | /** 23 | * Get an array of serviceAccounts. 24 | * 25 | * @param array|ServiceAccount> $items 26 | * @return array 27 | */ 28 | protected function getServiceAccounts(array $items) 29 | { 30 | $final = []; 31 | foreach ($items as &$item) { 32 | if (!$item instanceof ServiceAccount) { 33 | $final[] = new ServiceAccount($item); 34 | } else { 35 | $final[] = $item; 36 | } 37 | } 38 | 39 | return $final; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Collections/ServiceCollection.php: -------------------------------------------------------------------------------- 1 | getServices($items)); 13 | } 14 | 15 | /** 16 | * Get an array of services. 17 | */ 18 | protected function getServices(array $items): array 19 | { 20 | foreach ($items as &$item) { 21 | if ($item instanceof Service) { 22 | continue; 23 | } 24 | 25 | $item = new Service($item); 26 | } 27 | 28 | return $items; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Exceptions/ApiServerException.php: -------------------------------------------------------------------------------- 1 | attributes = $attributes; 50 | } else { 51 | throw new InvalidArgumentException('Attributes are not an array.'); 52 | } 53 | } elseif ($attributeType == self::MODEL_FROM_JSON) { 54 | if (!is_string($attributes[self::MODEL_FROM_JSON])) { 55 | throw new InvalidArgumentException('JSON attributes must be provided as a JSON encoded string.'); 56 | } 57 | 58 | try { 59 | $this->attributes[self::MODEL_FROM_JSON] = json_decode($attributes[self::MODEL_FROM_JSON], true, 512, JSON_THROW_ON_ERROR); 60 | } catch (JsonException $e) { 61 | throw new InvalidArgumentException('Failed to parse JSON encoded attributes: ' . $e->getMessage(), 0, $e); 62 | } 63 | } elseif ($attributeType == self::MODEL_FROM_YAML) { 64 | if (!is_string($attributes[self::MODEL_FROM_YAML])) { 65 | throw new InvalidArgumentException('YAML attributes must be provided as a YAML encoded string.'); 66 | } 67 | 68 | try { 69 | $this->attributes[self::MODEL_FROM_YAML] = Yaml::parse($attributes[self::MODEL_FROM_YAML]); 70 | } catch (YamlParseException $e) { 71 | throw new InvalidArgumentException('Failed to parse YAML encoded attributes: ' . $e->getMessage(), 0, $e); 72 | } 73 | } else { 74 | throw new InvalidArgumentException('Invalid attribute type: ' . $attributeType); 75 | } 76 | } 77 | 78 | /** 79 | * Get the model as an array. 80 | */ 81 | public function toArray(): array 82 | { 83 | return $this->attributes; 84 | } 85 | 86 | /** 87 | * Get some metadata. 88 | */ 89 | public function getMetadata(string $key) 90 | { 91 | return isset($this->attributes['metadata'][$key]) ? $this->attributes['metadata'][$key] : null; 92 | } 93 | 94 | /** 95 | * Get data ussing Json Path. 96 | * 97 | * @throws JSONPathException 98 | */ 99 | public function getJsonPath(string $expression): JSONPath 100 | { 101 | $jsonPath = new JSONPath($this->attributes); 102 | 103 | return $jsonPath->find($expression); 104 | } 105 | 106 | /** 107 | * Get the schema. 108 | */ 109 | public function getSchema(): string 110 | { 111 | if (!isset($this->schema['kind'])) { 112 | $this->schema['kind'] = basename(str_replace('\\', '/', get_class($this))); 113 | if ($this->pluralKind) { 114 | $this->schema['kind'] .= 's'; 115 | } 116 | } 117 | 118 | if (!isset($this->schema['apiVersion'])) { 119 | $this->schema['apiVersion'] = $this->apiVersion; 120 | } 121 | 122 | $schema = array_merge($this->schema, $this->toArray()); 123 | 124 | if(isset($schema[self::MODEL_FROM_YAML])) { 125 | $jsonSchema = json_encode($schema[self::MODEL_FROM_YAML], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); 126 | } elseif (isset($schema[self::MODEL_FROM_JSON])) { 127 | $jsonSchema = $schema[self::MODEL_FROM_JSON]; 128 | } else { 129 | $jsonSchema = json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); 130 | } 131 | 132 | 133 | // Fix for issue #37, can't use JSON_FORCE_OBJECT as the encoding breaks arrays of objects, for example port mappings. 134 | $jsonSchema = str_replace(': []', ': {}', $jsonSchema); 135 | 136 | return $jsonSchema; 137 | } 138 | 139 | /** 140 | * Get the api version. 141 | */ 142 | public function getApiVersion(): string 143 | { 144 | return $this->apiVersion; 145 | } 146 | 147 | /** 148 | * Set the api version. 149 | */ 150 | public function setApiVersion(string $apiVersion): void 151 | { 152 | $this->apiVersion = $apiVersion; 153 | } 154 | 155 | /** 156 | * Get the model as a string. 157 | */ 158 | public function __toString() 159 | { 160 | return $this->getSchema(); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Models/NamespaceModel.php: -------------------------------------------------------------------------------- 1 | 'Namespace']; 9 | } 10 | -------------------------------------------------------------------------------- /src/Models/NetworkPolicy.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Role extends Model 11 | { 12 | /** 13 | * @var string 14 | */ 15 | protected string $apiVersion = 'rbac.authorization.k8s.io/v1'; 16 | } 17 | -------------------------------------------------------------------------------- /src/Models/RoleBinding.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class RoleBinding extends Model 11 | { 12 | /** 13 | * @var string 14 | */ 15 | protected string $apiVersion = 'rbac.authorization.k8s.io/v1'; 16 | } 17 | -------------------------------------------------------------------------------- /src/Models/Secret.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ServiceAccount extends Model 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Repositories/CertificateRepository.php: -------------------------------------------------------------------------------- 1 | client->sendRequest($method, '/' . $this->uri . '/' . $node->getMetadata('name') . '/proxy/' . $proxy_uri, $queryParams, [], $this->namespace); 19 | return $response; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Repositories/PersistentVolumeClaimRepository.php: -------------------------------------------------------------------------------- 1 | getApiVersion(); 28 | if ($apiVersion == 'v1') { 29 | $apiVersion = null; 30 | } 31 | 32 | return $this->client->sendRequest($method, $uri, $query, $body, $namespace, $apiVersion); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Repositories/PodRepository.php: -------------------------------------------------------------------------------- 1 | client->sendRequest('GET', '/' . $this->uri . '/' . $pod->getMetadata('name') . '/log', $queryParams); 21 | return $response; 22 | } 23 | 24 | /** 25 | * Execute a command on a pod. 26 | * 27 | * @return mixed 28 | */ 29 | #[\ReturnTypeWillChange] 30 | public function exec(Pod $pod, array $queryParams = []) 31 | { 32 | $response = $this->client->sendRequest('POST', '/' . $this->uri . '/' . $pod->getMetadata('name') . '/exec', $queryParams); 33 | return $response; 34 | } 35 | 36 | /** 37 | * Attach an ephemeralContainer to a pod. 38 | * 39 | * @param Pod $pod Pod object 40 | * @param array $spec array representing the relevant strategic spec 41 | * @see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#ephemeralcontainer-v1-core EphemeralContainer spec 42 | * 43 | * @return array 44 | */ 45 | public function debug(Pod $pod, array $spec): array 46 | { 47 | $patch = json_encode($spec); 48 | 49 | $this->client->setPatchType('strategic'); 50 | 51 | return $this->sendRequest('PATCH', '/' . $this->uri . '/' . $pod->getMetadata('name') . '/ephemeralcontainers', [], $patch, $this->namespace); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Repositories/QuotaRepository.php: -------------------------------------------------------------------------------- 1 | client = $client; 64 | } 65 | 66 | /** 67 | * Send a request. 68 | * 69 | * @param mixed $body 70 | * @return mixed 71 | */ 72 | protected function sendRequest(string $method, string $uri, array $query = [], $body = [], bool $namespace = true, array $requestOptions = []) 73 | { 74 | $apiVersion = $this->getApiVersion(); 75 | if ($apiVersion === 'v1') { 76 | $apiVersion = null; 77 | } 78 | 79 | return $this->client->sendRequest($method, $uri, $query, $body, $namespace, $apiVersion, $requestOptions); 80 | } 81 | 82 | /** 83 | * Get the api version from the model. 84 | */ 85 | protected function getApiVersion(): ?string 86 | { 87 | if ($this->apiVersion) { 88 | return $this->apiVersion; 89 | } 90 | 91 | $className = str_replace('Repository', '', class_basename($this)); 92 | $classPath = $this->modelClassNamespace . $className; 93 | 94 | if (!class_exists($classPath)) { 95 | return null; 96 | } 97 | 98 | $this->apiVersion = (new $classPath)->getApiVersion(); 99 | 100 | return $this->apiVersion; 101 | } 102 | 103 | /** 104 | * Create a new model. 105 | */ 106 | public function create(Model $model): array 107 | { 108 | return $this->sendRequest('POST', '/' . $this->uri, [], $model->getSchema(), $this->namespace); 109 | } 110 | 111 | /** 112 | * Update a model. 113 | */ 114 | public function update(Model $model): array 115 | { 116 | return $this->sendRequest('PUT', '/' . $this->uri . '/' . $model->getMetadata('name'), [], $model->getSchema(), $this->namespace); 117 | } 118 | 119 | /** 120 | * Patch a model. 121 | */ 122 | public function patch(Model $model): array 123 | { 124 | return $this->sendRequest('PATCH', '/' . $this->uri . '/' . $model->getMetadata('name'), [], $model->getSchema(), $this->namespace); 125 | } 126 | 127 | /** 128 | * Apply a json patch to a model. 129 | */ 130 | public function applyJsonPatch(Model $model, array $patch): array 131 | { 132 | $patch = json_encode($patch); 133 | 134 | $this->client->setPatchType('json'); 135 | 136 | return $this->sendRequest('PATCH', '/' . $this->uri . '/' . $model->getMetadata('name'), [], $patch, $this->namespace); 137 | } 138 | 139 | /** 140 | * Apply a model. 141 | * 142 | * Creates a new api object if not exists, or patch. 143 | */ 144 | public function apply(Model $model): array 145 | { 146 | $exists = $this->exists((string)$model->getMetadata("name")); 147 | 148 | if ($exists) { 149 | return $this->patch($model); 150 | } else { 151 | return $this->create($model); 152 | } 153 | } 154 | 155 | /** 156 | * Delete a model. 157 | */ 158 | public function delete(Model $model, DeleteOptions $options = null): array 159 | { 160 | return $this->deleteByName((string)$model->getMetadata('name'), $options); 161 | } 162 | 163 | /** 164 | * Delete a model by name. 165 | */ 166 | public function deleteByName(string $name, DeleteOptions $options = null): array 167 | { 168 | $body = $options ? $options->getSchema() : null; 169 | 170 | return $this->sendRequest('DELETE', '/' . $this->uri . '/' . $name, [], $body, $this->namespace); 171 | } 172 | 173 | /** 174 | * Set the label selector including inequality search terms. 175 | */ 176 | public function setLabelSelector(array $labelSelector, array $inequalityLabelSelector=[]): Repository 177 | { 178 | $this->labelSelector = $labelSelector; 179 | $this->inequalityLabelSelector = $inequalityLabelSelector; 180 | return $this; 181 | } 182 | 183 | /** 184 | * Get the label selector query. 185 | */ 186 | protected function getLabelSelectorQuery(): string 187 | { 188 | $parts = []; 189 | foreach ($this->labelSelector as $key => $value) { 190 | $parts[] = null === $value ? $key : ($key . '=' . $value); 191 | } 192 | 193 | // If any inequality search terms are set, add them to the parts array 194 | if (!empty($this->inequalityLabelSelector)) { 195 | foreach ($this->inequalityLabelSelector as $key => $value) { 196 | $parts[] = $key . '!=' . $value; 197 | } 198 | } 199 | return implode(',', $parts); 200 | } 201 | 202 | /** 203 | * Set the field selector including inequality search terms. 204 | */ 205 | public function setFieldSelector(array $fieldSelector, array $inequalityFieldSelector=[]): Repository 206 | { 207 | $this->fieldSelector = $fieldSelector; 208 | $this->inequalityFieldSelector = $inequalityFieldSelector; 209 | return $this; 210 | } 211 | 212 | /** 213 | * Get the field selector query. 214 | */ 215 | protected function getFieldSelectorQuery(): string 216 | { 217 | $parts = []; 218 | foreach ($this->fieldSelector as $key => $value) { 219 | $parts[] = $key . '=' . $value; 220 | } 221 | 222 | // If any inequality search terms are set, add them to the parts array 223 | if (!empty($this->inequalityFieldSelector)) { 224 | foreach ($this->inequalityFieldSelector as $key => $value) { 225 | $parts[] = $key . '!=' . $value; 226 | } 227 | } 228 | 229 | return implode(',', $parts); 230 | } 231 | 232 | /** 233 | * Reset the parameters. 234 | */ 235 | protected function resetParameters(): void 236 | { 237 | $this->labelSelector = []; 238 | $this->fieldSelector = []; 239 | } 240 | 241 | /** 242 | * Get a collection of items. 243 | */ 244 | public function find(array $query = []): Collection 245 | { 246 | $query = array_filter(array_merge([ 247 | 'labelSelector' => $this->getLabelSelectorQuery(), 248 | 'fieldSelector' => $this->getFieldSelectorQuery(), 249 | ], $query), function ($value) { 250 | return !is_null($value) && strlen($value) > 0; 251 | }); 252 | 253 | $this->resetParameters(); 254 | 255 | $response = $this->sendRequest('GET', '/' . $this->uri, $query, null, $this->namespace); 256 | 257 | return $this->createCollection($response); 258 | } 259 | 260 | /** 261 | * Find the first item. 262 | */ 263 | public function first(): ?Model 264 | { 265 | return $this->find()->first(); 266 | } 267 | 268 | /** 269 | * Watch a model for changes. 270 | */ 271 | public function watch(Model $model, Closure $closure, array $query = []): void 272 | { 273 | $this->setFieldSelector([ 274 | 'metadata.name' => $model->getMetadata('name'), 275 | ]); 276 | 277 | $query = array_filter(array_merge([ 278 | 'watch' => true, 279 | 'timeoutSeconds' => 30, 280 | 'labelSelector' => $this->getLabelSelectorQuery(), 281 | 'fieldSelector' => $this->getFieldSelectorQuery(), 282 | ], $query), function ($value) { 283 | return !is_null($value) && strlen($value) > 0; 284 | }); 285 | 286 | $this->resetParameters(); 287 | 288 | $response = $this->sendRequest('GET', '/' . $this->uri, $query, null, $this->namespace, [ 289 | 'stream' => true, 290 | 'read_timeout' => 5, 291 | ]); 292 | 293 | $stream = $response->getBody(); 294 | 295 | $parser = new JSONStreamingParser($stream, new JSONStreamingListener($closure)); 296 | $parser->parse(); 297 | } 298 | 299 | /** 300 | * Check if an item exists by name. 301 | */ 302 | public function exists(string $name): bool 303 | { 304 | $this->resetParameters(); 305 | return !is_null($this->setFieldSelector(['metadata.name' => $name])->first()); 306 | } 307 | 308 | /** 309 | * Create a collection of models from the response. 310 | */ 311 | abstract protected function createCollection(array $response): Collection; 312 | } 313 | -------------------------------------------------------------------------------- /src/Repositories/RoleBindingRepository.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class RoleBindingRepository extends Repository 15 | { 16 | protected string $uri = 'rolebindings'; 17 | 18 | /** 19 | * @param array{items: array|RoleBinding>} $response 20 | */ 21 | protected function createCollection(array $response): Collection 22 | { 23 | return new RoleBindingCollection($response['items']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Repositories/RoleRepository.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class RoleRepository extends Repository 15 | { 16 | protected string $uri = 'roles'; 17 | 18 | /** 19 | * @param array{items: array|Role>} $response 20 | */ 21 | protected function createCollection(array $response): Collection 22 | { 23 | return new RoleCollection($response['items']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Repositories/SecretRepository.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ServiceAccountRepository extends Repository 15 | { 16 | protected string $uri = 'serviceaccounts'; 17 | 18 | /** 19 | * @param array{items: array|ServiceAccount>} $response 20 | */ 21 | protected function createCollection(array $response): Collection 22 | { 23 | return new ServiceAccountCollection($response['items']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Repositories/ServiceRepository.php: -------------------------------------------------------------------------------- 1 | client->setPatchType("merge"); 10 | 11 | $result = parent::patch($model); 12 | 13 | // Reverting default patch type 14 | $this->client->setPatchType(); 15 | 16 | return $result; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Repositories/Utils/JSONStreamingListener.php: -------------------------------------------------------------------------------- 1 | closure = $closure; 43 | } 44 | 45 | /** 46 | * Set the target from the given key 47 | */ 48 | public function setTargetFromKey(string $key) : self 49 | { 50 | // Turn the key using dot notation into an array 51 | $this->target = array_filter(explode('.', $key)); 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * Listen to the start of the document. 58 | */ 59 | public function startDocument() : void 60 | { 61 | // Ignore the start of the document 62 | } 63 | 64 | /** 65 | * Listen to the end of the document. 66 | */ 67 | public function endDocument() : void 68 | { 69 | // Free the memory at the end of the document 70 | $this->stack = []; 71 | $this->keyByDepth = []; 72 | $this->breadcrumbs = []; 73 | } 74 | 75 | /** 76 | * Listen to the start of the object. 77 | */ 78 | public function startObject() : void 79 | { 80 | // Every object increases the depth when it starts 81 | $this->depth++; 82 | 83 | // Only add objects within the target or all objects if no target is set 84 | if ($this->shouldBeExtracted()) { 85 | $this->stack[] = []; 86 | } 87 | } 88 | 89 | /** 90 | * Determine whether the current object should be extracted 91 | */ 92 | protected function shouldBeExtracted() : bool 93 | { 94 | // All JSON objects should be extracted if no target is set 95 | if (empty($this->target)) { 96 | return true; 97 | } 98 | 99 | // Determine whether the current JSON object is within the target 100 | $length = count($this->target); 101 | return array_slice($this->breadcrumbs, 0, $length) === $this->target; 102 | } 103 | 104 | /** 105 | * Listen to the end of the object. 106 | */ 107 | public function endObject() : void 108 | { 109 | // Every object decreases the depth when it ends 110 | $this->depth--; 111 | 112 | if ($this->shouldBeSkipped()) { 113 | return; 114 | } 115 | 116 | // When an object ends, update the current position to the object's parent key 117 | array_pop($this->breadcrumbs); 118 | 119 | $object = array_pop($this->stack); 120 | 121 | // If the stack is empty, the object has been fully extracted and can be processed 122 | // Otherwise it is a nested object to be paired to a key or added to an array 123 | if (empty($this->stack) && !empty($object)) { 124 | $this->processExtractedObject($object); 125 | } else { 126 | $this->value($object); 127 | } 128 | } 129 | 130 | /** 131 | * Determine whether the current object should be skipped 132 | */ 133 | protected function shouldBeSkipped() : bool 134 | { 135 | return !$this->shouldBeExtracted(); 136 | } 137 | 138 | /** 139 | * Process the given extracted object. 140 | */ 141 | protected function processExtractedObject(array $extractedObject): void 142 | { 143 | $type = isset($extractedObject['type']) ? $extractedObject['type'] : null; 144 | $object = isset($extractedObject['object']) ? $extractedObject['object'] : null; 145 | 146 | call_user_func_array($this->closure, [ 147 | $type, 148 | $object 149 | ]); 150 | } 151 | 152 | /** 153 | * Listen to the start of the array. 154 | */ 155 | public function startArray() : void 156 | { 157 | // If the document starts with an array, ignore it 158 | if ($this->depth === 0) { 159 | return; 160 | } 161 | 162 | $this->depth++; 163 | 164 | // Asterisks indicate that the current position is within an array 165 | if (!empty($this->target)) { 166 | $this->breadcrumbs[] = '*'; 167 | } 168 | 169 | // If the target is an array, extract its JSON objects but ignore the wrapping array 170 | if ($this->shouldBeExtracted() && !$this->isTarget()) { 171 | $this->stack[] = []; 172 | } 173 | } 174 | 175 | /** 176 | * Determine whether the current element is the target 177 | */ 178 | protected function isTarget() : bool 179 | { 180 | if (empty($this->target)) { 181 | return false; 182 | } 183 | 184 | // An element is the target if their positions and depths coincide 185 | return $this->shouldBeExtracted() && count($this->target) === $this->depth; 186 | } 187 | 188 | /** 189 | * Listen to the end of the array. 190 | */ 191 | public function endArray() : void 192 | { 193 | // If the document ends with an array, ignore it 194 | if ($this->depth === 0) { 195 | return; 196 | } 197 | 198 | $this->depth--; 199 | 200 | // Update the current position if a target is set 201 | if (!empty($this->target)) { 202 | array_pop($this->breadcrumbs); 203 | } 204 | 205 | // If the target is an array, extract its JSON objects but ignore the wrapping array 206 | if ($this->shouldBeSkipped() || $this->isTarget()) { 207 | return; 208 | } 209 | 210 | // The nested array is ready to be paired to a key or added to an array 211 | $this->value(array_pop($this->stack)); 212 | } 213 | 214 | /** 215 | * Listen to the key. 216 | */ 217 | public function key(string $key) : void 218 | { 219 | $this->keyByDepth[$this->depth] = $key; 220 | 221 | // Update the current position if a target is set 222 | if (!empty($this->target)) { 223 | $this->breadcrumbs[$this->depth - 1] = $key; 224 | $this->breadcrumbs = array_slice($this->breadcrumbs, 0, $this->depth); 225 | } 226 | } 227 | 228 | /** 229 | * Listen to the value. 230 | * 231 | * @param mixed $value 232 | */ 233 | public function value($value) : void 234 | { 235 | if ($this->shouldBeSkipped()) { 236 | return; 237 | } 238 | 239 | $object = array_pop($this->stack); 240 | 241 | // Pair the value to the current key if set or add the value to an array 242 | if (empty($this->keyByDepth[$this->depth])) { 243 | $object[] = $value; 244 | } else { 245 | $object[$this->keyByDepth[$this->depth]] = $value; 246 | $this->keyByDepth[$this->depth] = null; 247 | } 248 | 249 | $this->stack[] = $object; 250 | } 251 | 252 | /** 253 | * Listen to the whitespace. 254 | */ 255 | public function whitespace(string $whitespace) : void 256 | { 257 | // Ignore the whitespaces 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/Repositories/Utils/JSONStreamingParser.php: -------------------------------------------------------------------------------- 1 | stream = $stream; 80 | $this->listener = $listener; 81 | $this->emitWhitespace = $emitWhitespace; 82 | $this->state = self::STATE_START_DOCUMENT; 83 | $this->bufferSize = $bufferSize; 84 | $this->lineEnding = $lineEnding; 85 | } 86 | 87 | /** 88 | * Parse the stream. 89 | */ 90 | public function parse(): void 91 | { 92 | $this->lineNumber = 1; 93 | $this->charNumber = 1; 94 | $eof = false; 95 | 96 | while (!$this->stream->eof() && !$eof) { 97 | $pos = $this->stream->tell(); 98 | $line = $this->stream->read($this->bufferSize); 99 | 100 | $byteLen = strlen($line); 101 | for ($i = 0; $i < $byteLen; $i++) { 102 | $this->consumeChar($line[$i]); 103 | $this->charNumber++; 104 | 105 | if ($this->stopParsing) { 106 | return; 107 | } 108 | } 109 | } 110 | } 111 | 112 | public function stop(): void 113 | { 114 | $this->stopParsing = true; 115 | } 116 | 117 | private function consumeChar(string $char): void 118 | { 119 | // see https://en.wikipedia.org/wiki/Byte_order_mark 120 | if ($this->charNumber < 5 121 | && 1 === $this->lineNumber 122 | && $this->checkAndSkipUtfBom($char) 123 | ) { 124 | return; 125 | } 126 | 127 | // valid whitespace characters in JSON (from RFC4627 for JSON) include: 128 | // space, horizontal tab, line feed or new line, and carriage return. 129 | // thanks: http://stackoverflow.com/questions/16042274/definition-of-whitespace-in-json 130 | if ((' ' === $char || "\t" === $char || "\n" === $char || "\r" === $char) && 131 | !(self::STATE_IN_STRING === $this->state || 132 | self::STATE_UNICODE === $this->state || 133 | self::STATE_START_ESCAPE === $this->state || 134 | self::STATE_IN_NUMBER === $this->state) 135 | ) { 136 | // we wrap this so that we don't make a ton of unnecessary function calls 137 | // unless someone really, really cares about whitespace. 138 | if ($this->emitWhitespace) { 139 | $this->listener->whitespace($char); 140 | } 141 | 142 | return; 143 | } 144 | 145 | switch ($this->state) { 146 | case self::STATE_IN_STRING: 147 | if ('"' === $char) { 148 | $this->endString(); 149 | } elseif ('\\' === $char) { 150 | $this->state = self::STATE_START_ESCAPE; 151 | } elseif ($char < "\x1f") { 152 | $this->throwParseError('Unescaped control character encountered: '.$char); 153 | } else { 154 | $this->buffer .= $char; 155 | } 156 | break; 157 | 158 | case self::STATE_IN_ARRAY: 159 | if (']' === $char) { 160 | $this->endArray(); 161 | } else { 162 | $this->startValue($char); 163 | } 164 | break; 165 | 166 | case self::STATE_IN_OBJECT: 167 | if ('}' === $char) { 168 | $this->endObject(); 169 | } elseif ('"' === $char) { 170 | $this->startKey(); 171 | } else { 172 | $this->throwParseError('Start of string expected for object key. Instead got: '.$char); 173 | } 174 | break; 175 | 176 | case self::STATE_END_KEY: 177 | if (':' !== $char) { 178 | $this->throwParseError("Expected ':' after key."); 179 | } 180 | $this->state = self::STATE_AFTER_KEY; 181 | break; 182 | 183 | case self::STATE_AFTER_KEY: 184 | $this->startValue($char); 185 | break; 186 | 187 | case self::STATE_START_ESCAPE: 188 | $this->processEscapeCharacter($char); 189 | break; 190 | 191 | case self::STATE_UNICODE: 192 | $this->processUnicodeCharacter($char); 193 | break; 194 | 195 | case self::STATE_UNICODE_SURROGATE: 196 | $this->unicodeEscapeBuffer .= $char; 197 | if (2 === mb_strlen($this->unicodeEscapeBuffer)) { 198 | $this->endUnicodeSurrogateInterstitial(); 199 | } 200 | break; 201 | 202 | case self::STATE_AFTER_VALUE: 203 | $within = end($this->stack); 204 | if (self::STACK_OBJECT === $within) { 205 | if ('}' === $char) { 206 | $this->endObject(); 207 | } elseif (',' === $char) { 208 | $this->state = self::STATE_IN_OBJECT; 209 | } else { 210 | $this->throwParseError("Expected ',' or '}' while parsing object. Got: ".$char); 211 | } 212 | } elseif (self::STACK_ARRAY === $within) { 213 | if (']' === $char) { 214 | $this->endArray(); 215 | } elseif (',' === $char) { 216 | $this->state = self::STATE_IN_ARRAY; 217 | } else { 218 | $this->throwParseError("Expected ',' or ']' while parsing array. Got: ".$char); 219 | } 220 | } else { 221 | $this->throwParseError( 222 | 'Finished a literal, but unclear what state to move to. Last state: '.$within 223 | ); 224 | } 225 | break; 226 | 227 | case self::STATE_IN_NUMBER: 228 | if (ctype_digit($char)) { 229 | $this->buffer .= $char; 230 | } elseif ('.' === $char) { 231 | if (false !== strpos($this->buffer, '.')) { 232 | $this->throwParseError('Cannot have multiple decimal points in a number.'); 233 | } elseif (false !== stripos($this->buffer, 'e')) { 234 | $this->throwParseError('Cannot have a decimal point in an exponent.'); 235 | } 236 | $this->buffer .= $char; 237 | } elseif ('e' === $char || 'E' === $char) { 238 | if (false !== stripos($this->buffer, 'e')) { 239 | $this->throwParseError('Cannot have multiple exponents in a number.'); 240 | } 241 | $this->buffer .= $char; 242 | } elseif ('+' === $char || '-' === $char) { 243 | $last = mb_substr($this->buffer, -1); 244 | if (!('e' === $last || 'E' === $last)) { 245 | $this->throwParseError("Can only have '+' or '-' after the 'e' or 'E' in a number."); 246 | } 247 | $this->buffer .= $char; 248 | } else { 249 | $this->endNumber(); 250 | // we have consumed one beyond the end of the number 251 | $this->consumeChar($char); 252 | } 253 | break; 254 | 255 | case self::STATE_IN_TRUE: 256 | $this->buffer .= $char; 257 | if (4 === \strlen($this->buffer)) { 258 | $this->endTrue(); 259 | } 260 | break; 261 | 262 | case self::STATE_IN_FALSE: 263 | $this->buffer .= $char; 264 | if (5 === \strlen($this->buffer)) { 265 | $this->endFalse(); 266 | } 267 | break; 268 | 269 | case self::STATE_IN_NULL: 270 | $this->buffer .= $char; 271 | if (4 === \strlen($this->buffer)) { 272 | $this->endNull(); 273 | } 274 | break; 275 | 276 | case self::STATE_START_DOCUMENT: 277 | $this->listener->startDocument(); 278 | if ('[' === $char) { 279 | $this->startArray(); 280 | } elseif ('{' === $char) { 281 | $this->startObject(); 282 | } else { 283 | $this->throwParseError('Document must start with object or array.'); 284 | } 285 | break; 286 | 287 | case self::STATE_END_DOCUMENT: 288 | if ('[' !== $char && '{' !== $char) { 289 | $this->throwParseError('Expected end of document.'); 290 | } 291 | $this->state = self::STATE_START_DOCUMENT; 292 | $this->consumeChar($char); 293 | break; 294 | 295 | case self::STATE_DONE: 296 | $this->throwParseError('Expected end of document.'); 297 | break; 298 | 299 | default: 300 | $this->throwParseError('Internal error. Reached an unknown state: '.$this->state); 301 | break; 302 | } 303 | } 304 | 305 | private function checkAndSkipUtfBom(string $c): bool 306 | { 307 | if (1 === $this->charNumber) { 308 | if ($c === \chr(239)) { 309 | $this->utfBom = self::UTF8_BOM; 310 | } elseif ($c === \chr(254) || $c === \chr(255)) { 311 | // NOTE: could also be UTF32_BOM 312 | // second character will tell 313 | $this->utfBom = self::UTF16_BOM; 314 | } elseif ($c === \chr(0)) { 315 | $this->utfBom = self::UTF32_BOM; 316 | } 317 | } 318 | 319 | if (self::UTF16_BOM === $this->utfBom && 2 === $this->charNumber && 320 | $c === \chr(254)) { 321 | $this->utfBom = self::UTF32_BOM; 322 | } 323 | 324 | if (self::UTF8_BOM === $this->utfBom && $this->charNumber < 4) { 325 | // UTF-8 BOM starts with chr(239) . chr(187) . chr(191) 326 | return true; 327 | } 328 | if (self::UTF16_BOM === $this->utfBom && $this->charNumber < 3) { 329 | return true; 330 | } 331 | if (self::UTF32_BOM === $this->utfBom && $this->charNumber < 5) { 332 | return true; 333 | } 334 | 335 | return false; 336 | } 337 | 338 | /** 339 | * @throws ParsingException 340 | */ 341 | private function startValue(string $c): void 342 | { 343 | if ('[' === $c) { 344 | $this->startArray(); 345 | } elseif ('{' === $c) { 346 | $this->startObject(); 347 | } elseif ('"' === $c) { 348 | $this->startString(); 349 | } elseif (JSONStreamingParserHelper::isDigit($c)) { 350 | $this->startNumber($c); 351 | } elseif ('t' === $c) { 352 | $this->state = self::STATE_IN_TRUE; 353 | $this->buffer .= $c; 354 | } elseif ('f' === $c) { 355 | $this->state = self::STATE_IN_FALSE; 356 | $this->buffer .= $c; 357 | } elseif ('n' === $c) { 358 | $this->state = self::STATE_IN_NULL; 359 | $this->buffer .= $c; 360 | } else { 361 | $this->throwParseError('Unexpected character for value: '.$c); 362 | } 363 | } 364 | 365 | private function startArray(): void 366 | { 367 | $this->listener->startArray(); 368 | $this->state = self::STATE_IN_ARRAY; 369 | $this->stack[] = self::STACK_ARRAY; 370 | } 371 | 372 | private function endArray(): void 373 | { 374 | $popped = array_pop($this->stack); 375 | if (self::STACK_ARRAY !== $popped) { 376 | $this->throwParseError('Unexpected end of array encountered.'); 377 | } 378 | $this->listener->endArray(); 379 | $this->state = self::STATE_AFTER_VALUE; 380 | 381 | if (empty($this->stack)) { 382 | $this->endDocument(); 383 | } 384 | } 385 | 386 | private function startObject(): void 387 | { 388 | $this->listener->startObject(); 389 | $this->state = self::STATE_IN_OBJECT; 390 | $this->stack[] = self::STACK_OBJECT; 391 | } 392 | 393 | private function endObject(): void 394 | { 395 | $popped = array_pop($this->stack); 396 | if (self::STACK_OBJECT !== $popped) { 397 | $this->throwParseError('Unexpected end of object encountered.'); 398 | } 399 | $this->listener->endObject(); 400 | $this->state = self::STATE_AFTER_VALUE; 401 | 402 | if (empty($this->stack)) { 403 | $this->endDocument(); 404 | } 405 | } 406 | 407 | private function startString(): void 408 | { 409 | $this->stack[] = self::STACK_STRING; 410 | $this->state = self::STATE_IN_STRING; 411 | } 412 | 413 | private function startKey(): void 414 | { 415 | $this->stack[] = self::STACK_KEY; 416 | $this->state = self::STATE_IN_STRING; 417 | } 418 | 419 | private function endString(): void 420 | { 421 | $popped = array_pop($this->stack); 422 | if (self::STACK_KEY === $popped) { 423 | $this->listener->key($this->buffer); 424 | $this->state = self::STATE_END_KEY; 425 | } elseif (self::STACK_STRING === $popped) { 426 | $this->listener->value($this->buffer); 427 | $this->state = self::STATE_AFTER_VALUE; 428 | } else { 429 | $this->throwParseError('Unexpected end of string.'); 430 | } 431 | $this->buffer = ''; 432 | } 433 | 434 | /** 435 | * @throws ParsingException 436 | */ 437 | private function processEscapeCharacter(string $c): void 438 | { 439 | if ('"' === $c) { 440 | $this->buffer .= '"'; 441 | } elseif ('\\' === $c) { 442 | $this->buffer .= '\\'; 443 | } elseif ('/' === $c) { 444 | $this->buffer .= '/'; 445 | } elseif ('b' === $c) { 446 | $this->buffer .= "\x08"; 447 | } elseif ('f' === $c) { 448 | $this->buffer .= "\f"; 449 | } elseif ('n' === $c) { 450 | $this->buffer .= "\n"; 451 | } elseif ('r' === $c) { 452 | $this->buffer .= "\r"; 453 | } elseif ('t' === $c) { 454 | $this->buffer .= "\t"; 455 | } elseif ('u' === $c) { 456 | $this->state = self::STATE_UNICODE; 457 | } else { 458 | $this->throwParseError('Expected escaped character after backslash. Got: '.$c); 459 | } 460 | 461 | if (self::STATE_UNICODE !== $this->state) { 462 | $this->state = self::STATE_IN_STRING; 463 | } 464 | } 465 | 466 | /** 467 | * @throws ParsingException 468 | */ 469 | private function processUnicodeCharacter(string $char): void 470 | { 471 | if (!JSONStreamingParserHelper::isHexCharacter($char)) { 472 | $this->throwParseError( 473 | 'Expected hex character for escaped Unicode character. ' 474 | .'Unicode parsed: '.implode('', $this->unicodeBuffer).' and got: '.$char 475 | ); 476 | } 477 | $this->unicodeBuffer[] = $char; 478 | if (4 === \count($this->unicodeBuffer)) { 479 | $codepoint = hexdec(implode('', $this->unicodeBuffer)); 480 | 481 | if ($codepoint >= 0xD800 && $codepoint < 0xDC00) { 482 | $this->unicodeHighSurrogate = $codepoint; 483 | $this->unicodeBuffer = []; 484 | $this->state = self::STATE_UNICODE_SURROGATE; 485 | } elseif ($codepoint >= 0xDC00 && $codepoint <= 0xDFFF) { 486 | if (-1 === $this->unicodeHighSurrogate) { 487 | $this->throwParseError('Missing high surrogate for Unicode low surrogate.'); 488 | } 489 | $combinedCodepoint = (($this->unicodeHighSurrogate - 0xD800) * 0x400) + ($codepoint - 0xDC00) + 0x10000; 490 | 491 | $this->endUnicodeCharacter($combinedCodepoint); 492 | } else { 493 | if (-1 !== $this->unicodeHighSurrogate) { 494 | $this->throwParseError('Invalid low surrogate following Unicode high surrogate.'); 495 | } else { 496 | $this->endUnicodeCharacter($codepoint); 497 | } 498 | } 499 | } 500 | } 501 | 502 | private function endUnicodeSurrogateInterstitial(): void 503 | { 504 | $unicodeEscape = $this->unicodeEscapeBuffer; 505 | if ('\\u' !== $unicodeEscape) { 506 | $this->throwParseError("Expected '\\u' following a Unicode high surrogate. Got: ".$unicodeEscape); 507 | } 508 | $this->unicodeEscapeBuffer = ''; 509 | $this->state = self::STATE_UNICODE; 510 | } 511 | 512 | private function endUnicodeCharacter(int $codepoint): void 513 | { 514 | $this->buffer .= JSONStreamingParserHelper::convertCodepointToCharacter($codepoint); 515 | $this->unicodeBuffer = []; 516 | $this->unicodeHighSurrogate = -1; 517 | $this->state = self::STATE_IN_STRING; 518 | } 519 | 520 | private function startNumber(string $c): void 521 | { 522 | $this->state = self::STATE_IN_NUMBER; 523 | $this->buffer .= $c; 524 | } 525 | 526 | private function endNumber(): void 527 | { 528 | $this->listener->value(JSONStreamingParserHelper::convertToNumber($this->buffer)); 529 | $this->buffer = ''; 530 | $this->state = self::STATE_AFTER_VALUE; 531 | } 532 | 533 | private function endTrue(): void 534 | { 535 | $this->endSpecialValue(true, 'true'); 536 | } 537 | 538 | private function endFalse(): void 539 | { 540 | $this->endSpecialValue(false, 'false'); 541 | } 542 | 543 | private function endNull(): void 544 | { 545 | $this->endSpecialValue(null, 'null'); 546 | } 547 | 548 | private function endSpecialValue($value, string $stringValue): void 549 | { 550 | if ($this->buffer === $stringValue) { 551 | $this->listener->value($value); 552 | } else { 553 | $this->throwParseError("Expected 'null'. Got: ".$this->buffer); 554 | } 555 | $this->buffer = ''; 556 | $this->state = self::STATE_AFTER_VALUE; 557 | } 558 | 559 | private function endDocument(): void 560 | { 561 | $this->listener->endDocument(); 562 | $this->state = self::STATE_END_DOCUMENT; 563 | } 564 | 565 | /** 566 | * @throws ParsingException 567 | */ 568 | private function throwParseError(string $message): void 569 | { 570 | throw new ParsingException($this->lineNumber, $this->charNumber, $message); 571 | } 572 | } 573 | -------------------------------------------------------------------------------- /src/Repositories/Utils/JSONStreamingParserHelper.php: -------------------------------------------------------------------------------- 1 | > 6) + 192).\chr(($char & 63) + 128); 23 | } 24 | if ($char <= 0xFFFF) { 25 | return \chr(($char >> 12) + 224).\chr((($char >> 6) & 63) + 128).\chr(($char & 63) + 128); 26 | } 27 | if ($char <= 0x1FFFFF) { 28 | return \chr(($char >> 18) + 240) 29 | .\chr((($char >> 12) & 63) + 128) 30 | .\chr((($char >> 6) & 63) + 128) 31 | .\chr(($char & 63) + 128); 32 | } 33 | 34 | return ''; 35 | } 36 | 37 | public static function convertToNumber(string $text) 38 | { 39 | // thanks to #andig for the fix for big integers 40 | if (ctype_digit($text) && (float) $text === (float) ((int) $text)) { 41 | // natural number PHP_INT_MIN < $num < PHP_INT_MAX 42 | return (int) $text; 43 | } 44 | 45 | // real number or natural number outside PHP_INT_MIN ... PHP_INT_MAX 46 | return (float) $text; 47 | } 48 | } -------------------------------------------------------------------------------- /src/RepositoryRegistry.php: -------------------------------------------------------------------------------- 1 | Repositories\NodeRepository::class, 11 | 'quotas' => Repositories\QuotaRepository::class, 12 | 'pods' => Repositories\PodRepository::class, 13 | 'replicaSets' => Repositories\ReplicaSetRepository::class, 14 | 'replicationControllers' => Repositories\ReplicationControllerRepository::class, 15 | 'services' => Repositories\ServiceRepository::class, 16 | 'secrets' => Repositories\SecretRepository::class, 17 | 'events' => Repositories\EventRepository::class, 18 | 'configMaps' => Repositories\ConfigMapRepository::class, 19 | 'endpoints' => Repositories\EndpointRepository::class, 20 | 'persistentVolume' => Repositories\PersistentVolumeRepository::class, 21 | 'persistentVolumeClaims' => Repositories\PersistentVolumeClaimRepository::class, 22 | 'namespaces' => Repositories\NamespaceRepository::class, 23 | 'serviceAccounts' => Repositories\ServiceAccountRepository::class, 24 | 25 | // batch/v1 26 | 'jobs' => Repositories\JobRepository::class, 27 | 28 | // batch/v2 29 | 'cronJobs' => Repositories\CronJobRepository::class, 30 | 31 | // apps/v1 32 | 'deployments' => Repositories\DeploymentRepository::class, 33 | 34 | // extensions/v1 35 | 'daemonSets' => Repositories\DaemonSetRepository::class, 36 | 'ingresses' => Repositories\IngressRepository::class, 37 | 38 | // autoscaling/v2 39 | 'horizontalPodAutoscalers' => Repositories\HorizontalPodAutoscalerRepository::class, 40 | 41 | // networking.k8s.io/v1 42 | 'networkPolicies' => Repositories\NetworkPolicyRepository::class, 43 | 44 | // certmanager.k8s.io/v1 45 | 'certificates' => Repositories\CertificateRepository::class, 46 | 'issuers' => Repositories\IssuerRepository::class, 47 | 48 | //rbac.authorization.k8s.io/v1 49 | 'roles' => Repositories\RoleRepository::class, 50 | 'roleBindings' => Repositories\RoleBindingRepository::class, 51 | ]; 52 | 53 | public function __construct() 54 | { 55 | 56 | } 57 | 58 | public function offsetExists($offset): bool 59 | { 60 | return isset($this->map[$offset]); 61 | } 62 | 63 | #[\ReturnTypeWillChange] 64 | public function offsetGet($offset) 65 | { 66 | return $this->map[$offset]; 67 | } 68 | 69 | public function offsetSet($offset, $value): void 70 | { 71 | $this->map[$offset] = $value; 72 | } 73 | 74 | public function offsetUnset($offset): void 75 | { 76 | unset($this->map[$offset]); 77 | } 78 | 79 | public function count(): int 80 | { 81 | return count($this->map); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/ClientTest.php: -------------------------------------------------------------------------------- 1 | setAccessible(true); 19 | $client = new Client([ 20 | 'master' => 'https://api.example.com', 21 | ]); 22 | 23 | $mockClientInterface = $this->getMockBuilder(ClientInterface::class) 24 | ->setMethods(['sendRequest']) 25 | ->getMock(); 26 | 27 | $jsonBody = json_encode([ 28 | 'message' => 'Hello world', 29 | ]); 30 | 31 | $response = new Response(200, [], $jsonBody); 32 | 33 | $mockClientInterface->expects($this->once()) 34 | ->method('sendRequest') 35 | ->withAnyParameters() 36 | ->willReturn($response); 37 | 38 | $httpClient = new HttpMethodsClient($mockClientInterface, new Psr17Factory()); 39 | 40 | $httpClientProp->setValue($client, $httpClient); 41 | 42 | $result = $client->sendRequest('GET', '/v1/poddy/'); 43 | 44 | $this->assertSame([ 45 | 'message' => 'Hello world', 46 | ], $result); 47 | } 48 | 49 | /** 50 | * Helper function for tests. Pass in a valid Client and have a fake response set on it. 51 | * @param Client $client 52 | * @param array $mockResponseData Response body (will be JSON encoded) 53 | * @param array $expectedSendArgs Expected arguments of ->send() method 54 | * @param int $respStatusCode Response status code 55 | * @param array $respHeaders Response headers (key => value map) 56 | */ 57 | private function setMockHttpResponse( 58 | Client $client, 59 | array $mockResponseData, 60 | array $expectedSendArgs, 61 | int $respStatusCode = 200, 62 | array $respHeaders = [] 63 | ): void { 64 | $httpClientProp = new ReflectionProperty(Client::class, 'httpClient'); 65 | $httpClientProp->setAccessible(true); 66 | 67 | $mockHttpMethodsClient = $this->getMockBuilder(HttpMethodsMockClient::class) 68 | ->setMethods(['send']) 69 | ->getMock(); 70 | 71 | $jsonBody = json_encode($mockResponseData); 72 | 73 | $response = new Response($respStatusCode, $respHeaders, $jsonBody); 74 | 75 | $mockHttpMethodsClient->expects($this->once()) 76 | ->method('send') 77 | ->with(...$expectedSendArgs) 78 | ->willReturn($response); 79 | 80 | $httpClientProp->setValue($client, $mockHttpMethodsClient); 81 | } 82 | 83 | public function testGetPodsFromApi(): void 84 | { 85 | $client = new Client(); 86 | 87 | $jsonBody = [ 88 | 'items' => [ 89 | [], 90 | [], 91 | [], 92 | ], 93 | ]; 94 | 95 | $this->setMockHttpResponse($client, $jsonBody, ['GET', '/api/' . $this->apiVersion . '/namespaces/' . $this->namespace . '/pods']); 96 | 97 | $result = $client->pods()->find(); 98 | 99 | $this->assertInstanceOf(PodCollection::class, $result); 100 | 101 | $this->assertSame(3, $result->count()); 102 | 103 | $pod1 = $result->first(); 104 | $this->assertInstanceOf(Pod::class, $pod1); 105 | } 106 | 107 | public function providerForFailedResponses(): array 108 | { 109 | return [ 110 | [ 111 | 500, 112 | \Maclof\Kubernetes\Exceptions\ApiServerException::class, 113 | '/500 Error/', 114 | ], 115 | [ 116 | 401, 117 | \Maclof\Kubernetes\Exceptions\ApiServerException::class, 118 | '/Authentication Exception/', 119 | ], 120 | [ 121 | 403, 122 | \Maclof\Kubernetes\Exceptions\ApiServerException::class, 123 | '/Authentication Exception/', 124 | ], 125 | ]; 126 | } 127 | 128 | /** 129 | * @dataProvider providerForFailedResponses 130 | */ 131 | public function testExceptionIsThrownOnFailureResponse(int $respCode, string $exceptionClass, string $msgRegEx): void 132 | { 133 | $client = new Client(); 134 | 135 | $this->setMockHttpResponse( 136 | $client, 137 | ['message' => 'Error hath occurred'], 138 | ["GET", "/api/v1/namespaces/default/api/anything", [], null], 139 | $respCode 140 | ); 141 | 142 | $this->expectException($exceptionClass); 143 | if (method_exists($this, 'expectExceptionMessageRegExp')) { 144 | $this->expectExceptionMessageRegExp($msgRegEx); 145 | } else { 146 | $this->expectExceptionMessageMatches($msgRegEx); 147 | } 148 | $client->sendRequest('GET', '/api/anything'); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/RepositoryRegistryTest.php: -------------------------------------------------------------------------------- 1 | assertCount(25, $registry); 13 | } 14 | 15 | public function test_add_repository(): void 16 | { 17 | $registry = new RepositoryRegistry(); 18 | $class = '\Example\Class'; 19 | 20 | $this->assertFalse(isset($registry['test'])); 21 | 22 | $registry['test'] = $class; 23 | 24 | $this->assertTrue(isset($registry['test'])); 25 | $this->assertEquals($class, $registry['test']); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $configMapCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $certificateCollection = $this->getCertificateCollection(); 23 | $items = $certificateCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/ConfigMapCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $configMapCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $configMapCollection = $this->getConfigMapCollection(); 23 | $items = $configMapCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/DeploymentCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $deploymentCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $deploymentCollection = $this->getDeploymentCollection(); 23 | $items = $deploymentCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/EndpointCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $nodeCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $nodeCollection = $this->getEndpointCollection(); 23 | $items = $nodeCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/EventCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $eventCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $eventCollection = $this->getEventCollection(); 23 | $items = $eventCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/IngressCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $ingressCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $ingressCollection = $this->getIngressCollection(); 23 | $items = $ingressCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/IssuerCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $issuerCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $issuerCollection = $this->getIssuerCollection(); 23 | $items = $issuerCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/JobCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $jobCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $jobCollection = $this->getJobCollection(); 23 | $items = $jobCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/NamespaceCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $namespaceCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $namespaceCollection = $this->getNamespaceCollection(); 23 | $items = $namespaceCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/NetworkPolicyCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $podCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $podCollection = $this->getNetworkPolicyCollection(); 23 | $items = $podCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/NodeCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $nodeCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $nodeCollection = $this->getNodeCollection(); 23 | $items = $nodeCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/PodCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $podCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $podCollection = $this->getPodCollection(); 23 | $items = $podCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/ReplicaSetCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $replicaSetCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $replicaSetCollection = $this->getReplicaSetCollection(); 23 | $items = $replicaSetCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/ReplicationControllerCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $replicationControllerCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $replicationControllerCollection = $this->getReplicationControllerCollection(); 23 | $items = $replicationControllerCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/RoleBindingCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $roleBindingCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $roleBindingCollection = $this->getRoleBindingCollection(); 23 | $items = $roleBindingCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/RoleCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $roleCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $roleCollection = $this->getRoleCollection(); 23 | $items = $roleCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/SecretCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $secretCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $secretCollection = $this->getSecretCollection(); 23 | $items = $secretCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/ServiceAccountCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $serviceAccountCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $serviceAccountCollection = $this->getServiceAccountCollection(); 23 | $items = $serviceAccountCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/collections/ServiceCollectionTest.php: -------------------------------------------------------------------------------- 1 | items); 16 | 17 | return $serviceCollection; 18 | } 19 | 20 | public function test_get_items(): void 21 | { 22 | $serviceCollection = $this->getServiceCollection(); 23 | $items = $serviceCollection->toArray(); 24 | 25 | $this->assertTrue(is_array($items)); 26 | $this->assertEquals(3, count($items)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/fixtures/certificates/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Certificate", 3 | "apiVersion": "certmanager.k8s.io/v1alpha1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/config-maps/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ConfigMap", 3 | "apiVersion": "v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/cron-jobs/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "CronJob", 3 | "apiVersion": "batch/v1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/deployments/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Deployment", 3 | "apiVersion": "apps/v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/endpoints/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Endpoints", 3 | "apiVersion": "v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/events/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Event", 3 | "apiVersion": "v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/ingresses/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Ingress", 3 | "apiVersion": "networking.k8s.io/v1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/issuers/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Issuer", 3 | "apiVersion": "certmanager.k8s.io/v1alpha1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/jobs/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Job", 3 | "apiVersion": "batch/v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/namespaces/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Namespace", 3 | "apiVersion": "v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/network-policies/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "NetworkPolicy", 3 | "apiVersion": "networking.k8s.io/v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/nodes/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Node", 3 | "apiVersion": "v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/pods/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Pod", 3 | "apiVersion": "v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/replica-sets/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ReplicaSet", 3 | "apiVersion": "apps/v1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/replication-controllers/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ReplicationController", 3 | "apiVersion": "v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/roles-bindings/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "RoleBinding", 3 | "apiVersion": "rbac.authorization.k8s.io/v1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/roles/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Role", 3 | "apiVersion": "rbac.authorization.k8s.io/v1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/secrets/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Secret", 3 | "apiVersion": "v1" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/services-accounts/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ServiceAccount", 3 | "apiVersion": "v1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/services/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Service", 3 | "apiVersion": "v1" 4 | } -------------------------------------------------------------------------------- /tests/helpers/HttpMethodsMockClient.php: -------------------------------------------------------------------------------- 1 | getSchema()); 12 | $fixture = trim($this->getFixture('certificates/empty.json')); 13 | 14 | $this->assertEquals($fixture, $schema); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $certificate = new Certificate([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $certificate->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/ConfigMapTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('config-maps/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $configMap = new ConfigMap([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $configMap->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/CronJobTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('cron-jobs/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $cronJob = new CronJob([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $cronJob->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | 30 | public function test_set_api_version(): void 31 | { 32 | $cronJob = new CronJob; 33 | 34 | $this->assertEquals($cronJob->getApiVersion(), 'batch/v1'); 35 | 36 | $cronJob->setApiVersion('batch/v2alpha1'); 37 | $this->assertEquals($cronJob->getApiVersion(), 'batch/v2alpha1'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/models/DeploymentTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('deployments/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $deployment = new Deployment([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $deployment->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/EndpointTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('endpoints/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $node = new Endpoint([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $node->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/EventTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('events/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $event = new Event([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $event->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/IngressTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('ingresses/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $ingress = new Ingress([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $ingress->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/IssuerTest.php: -------------------------------------------------------------------------------- 1 | getSchema()); 12 | $fixture = trim($this->getFixture('issuers/empty.json')); 13 | 14 | $this->assertEquals($fixture, $schema); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $issuer = new Issuer([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $issuer->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/JobTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('jobs/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $job = new Job([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $job->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/ModelTest.php: -------------------------------------------------------------------------------- 1 | ['name' => 'foo']]); 15 | 16 | $actual = $model->getJsonPath('$.metadata.name'); 17 | 18 | $this->assertInstanceOf(JSONPath::class, $actual); 19 | $this->assertCount(1, $actual); 20 | $this->assertEquals($expected, $actual[0]); 21 | } 22 | 23 | public function testIsGettingSchema(): void 24 | { 25 | $expected = json_encode([ 26 | 'kind' => 'ConcreteModel', 27 | 'apiVersion' => 'v1', 28 | ], JSON_PRETTY_PRINT); 29 | 30 | $model = new ConcreteModel([]); 31 | 32 | $actual = $model->getSchema(); 33 | 34 | $this->assertJson($actual); 35 | $this->assertEquals($expected, $actual); 36 | } 37 | 38 | public function testGetApiVersion(): void 39 | { 40 | $expected = 'v1'; 41 | 42 | $model = new ConcreteModel(); 43 | 44 | $actual = $model->getApiVersion(); 45 | 46 | $this->assertEquals($expected, $actual); 47 | } 48 | 49 | public function testGetMetadata(): void 50 | { 51 | $model = new ConcreteModel(['metadata' => ['name' => 'foo', 'labels' => ['foo' => 'bar']]]); 52 | 53 | $expected = 'foo'; 54 | 55 | $actual = $model->getMetadata('name'); 56 | 57 | $this->assertEquals($expected, $actual); 58 | 59 | $expected = ['foo' => 'bar']; 60 | 61 | $actual = $model->getMetadata('labels'); 62 | 63 | $this->assertEquals($expected, $actual); 64 | } 65 | 66 | public function testIsConvertingToArray(): void 67 | { 68 | $expected = ['foo' => 'bar']; 69 | 70 | $model = new ConcreteModel(['foo' => 'bar']); 71 | 72 | $actual = $model->toArray(); 73 | 74 | $this->assertEquals($expected, $actual); 75 | 76 | } 77 | 78 | public function testIsReturningSchemaOnToStringCall(): void 79 | { 80 | $model = new ConcreteModel(); 81 | 82 | $actual = (string) $model; 83 | 84 | $this->assertEquals($model->getSchema(), $actual); 85 | } 86 | } 87 | 88 | class ConcreteModel extends Model {} 89 | -------------------------------------------------------------------------------- /tests/models/NamespaceTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('namespaces/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $node = new NamespaceModel([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $node->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/NetworkPolicyTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('network-policies/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $policy = new NetworkPolicy([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $policy->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/NodeTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('nodes/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $node = new Node([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $node->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/PodTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('pods/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $pod = new Pod([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $pod->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/ReplicaSetTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('replica-sets/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $replicaSet = new ReplicaSet([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $replicaSet->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/ReplicationControllerTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('replication-controllers/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $replicationController = new ReplicationController([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $replicationController->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/RoleBindingTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('roles-bindings/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $roleBinding = new RoleBinding([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $roleBinding->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/RoleTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('roles/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $role = new Role([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $role->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/SecretTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('secrets/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $secret = new Secret([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $secret->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/ServiceAccountTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('services-accounts/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $serviceAccount = new ServiceAccount([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $serviceAccount->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/models/ServiceTest.php: -------------------------------------------------------------------------------- 1 | getSchema(); 12 | $fixture = $this->getFixture('services/empty.json'); 13 | 14 | $this->assertEquals($schema, $fixture); 15 | } 16 | 17 | public function test_get_metadata(): void 18 | { 19 | $service = new Service([ 20 | 'metadata' => [ 21 | 'name' => 'test', 22 | ], 23 | ]); 24 | 25 | $metadata = $service->getMetadata('name'); 26 | 27 | $this->assertEquals($metadata, 'test'); 28 | } 29 | } 30 | --------------------------------------------------------------------------------