├── .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 | [](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 |
--------------------------------------------------------------------------------