diff --git a/composer.json b/composer.json index 3dcd933..765bb45 100755 --- a/composer.json +++ b/composer.json @@ -19,7 +19,9 @@ }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.19.*" + "utopia-php/cli": "0.19.*", + "phrity/websocket": "^3.2", + "utopia-php/system": "0.9.*" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/composer.lock b/composer.lock index e41dcbf..45bddc2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,398 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "981afa1d268dd00d4f47a3e027193a7c", + "content-hash": "00e78d60d52b0280d884b3805dfd9841", "packages": [ + { + "name": "phrity/net-stream", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-net-stream.git", + "reference": "875d87c246cfacd66feaf6b0390f1e5d6985ad54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-net-stream/zipball/875d87c246cfacd66feaf6b0390f1e5d6985ad54", + "reference": "875d87c246cfacd66feaf6b0390f1e5d6985ad54", + "shasum": "" + }, + "require": { + "php": "^8.0", + "phrity/util-errorhandler": "^1.1", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 | ^2.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0 | ^11.0", + "phrity/net-uri": "^2.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Net\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "Socket stream classes implementing PSR-7 Stream and PSR-17 StreamFactory", + "homepage": "https://phrity.sirn.se/net-stream", + "keywords": [ + "Socket", + "client", + "psr-17", + "psr-7", + "server", + "stream", + "stream factory" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-net-stream/issues", + "source": "https://github.com/sirn-se/phrity-net-stream/tree/2.1.0" + }, + "time": "2024-09-14T12:03:20+00:00" + }, + { + "name": "phrity/net-uri", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-net-uri.git", + "reference": "841190135af4fab18135226aaaf99ec5791377ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-net-uri/zipball/841190135af4fab18135226aaaf99ec5791377ac", + "reference": "841190135af4fab18135226aaaf99ec5791377ac", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 | ^2.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0 | ^11.0", + "phrity/util-errorhandler": "^1.1", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-intl": "Enables IDN conversion for non-ASCII domains" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Net\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "PSR-7 Uri and PSR-17 UriFactory implementation", + "homepage": "https://phrity.sirn.se/net-uri", + "keywords": [ + "psr-17", + "psr-7", + "uri", + "uri factory" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-net-uri/issues", + "source": "https://github.com/sirn-se/phrity-net-uri/tree/2.1.0" + }, + "time": "2024-07-08T06:14:09+00:00" + }, + { + "name": "phrity/util-errorhandler", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-util-errorhandler.git", + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-util-errorhandler/zipball/483228156e06673963902b1cc1e6bd9541ab4d5e", + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e", + "shasum": "" + }, + "require": { + "php": "^7.4 | ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0 | ^11.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Util\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "Inline error handler; catch and resolve errors for code block.", + "homepage": "https://phrity.sirn.se/util-errorhandler", + "keywords": [ + "error", + "warning" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-util-errorhandler/issues", + "source": "https://github.com/sirn-se/phrity-util-errorhandler/tree/1.1.1" + }, + "time": "2024-09-12T06:49:16+00:00" + }, + { + "name": "phrity/websocket", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/websocket-php.git", + "reference": "5488e59e7e68d9e129a112fc7ede6e72a2c83b01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/websocket-php/zipball/5488e59e7e68d9e129a112fc7ede6e72a2c83b01", + "reference": "5488e59e7e68d9e129a112fc7ede6e72a2c83b01", + "shasum": "" + }, + "require": { + "php": "^8.1", + "phrity/net-stream": "^2.1", + "phrity/net-uri": "^2.1", + "psr/http-message": "^1.1 | ^2.0", + "psr/log": "^1.0 | ^2.0 | ^3.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^10.0 | ^11.0", + "phrity/net-mock": "^2.1", + "phrity/util-errorhandler": "^1.1", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "WebSocket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Fredrik Liljegren" + }, + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "WebSocket client and server", + "homepage": "https://phrity.sirn.se/websocket", + "keywords": [ + "client", + "server", + "websocket" + ], + "support": { + "issues": "https://github.com/sirn-se/websocket-php/issues", + "source": "https://github.com/sirn-se/websocket-php/tree/3.2.0" + }, + "time": "2024-09-28T08:44:22+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, { "name": "utopia-php/cli", "version": "0.19.0", @@ -207,6 +597,62 @@ "source": "https://github.com/utopia-php/servers/tree/0.1.1" }, "time": "2024-09-06T02:25:56+00:00" + }, + { + "name": "utopia-php/system", + "version": "0.9.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/system.git", + "reference": "8e4a7edaf2dfeb4c9524e9f766d27754f2c4b64d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/system/zipball/8e4a7edaf2dfeb4c9524e9f766d27754f2c4b64d", + "reference": "8e4a7edaf2dfeb4c9524e9f766d27754f2c4b64d", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "require-dev": { + "laravel/pint": "1.13.*", + "phpstan/phpstan": "1.10.*", + "phpunit/phpunit": "9.6.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\System\\": "src/System" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + }, + { + "name": "Torsten Dittmann", + "email": "torsten@appwrite.io" + } + ], + "description": "A simple library for obtaining information about the host's system.", + "keywords": [ + "framework", + "php", + "system", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/system/issues", + "source": "https://github.com/utopia-php/system/tree/0.9.0" + }, + "time": "2024-10-09T14:44:01+00:00" } ], "packages-dev": [ @@ -282,16 +728,16 @@ }, { "name": "laravel/pint", - "version": "v1.18.1", + "version": "v1.18.2", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9" + "reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9", + "url": "https://api.github.com/repos/laravel/pint/zipball/f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64", + "reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64", "shasum": "" }, "require": { @@ -344,7 +790,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-09-24T17:22:50+00:00" + "time": "2024-11-20T09:33:46+00:00" }, { "name": "myclabs/deep-copy", diff --git a/src/Orchestration/Adapter/KubernetesAPI.php b/src/Orchestration/Adapter/KubernetesAPI.php new file mode 100644 index 0000000..9ba623e --- /dev/null +++ b/src/Orchestration/Adapter/KubernetesAPI.php @@ -0,0 +1,408 @@ +namespace = $namespace; + + if ($username && $password && $email) { + $registryAuth = base64_encode(json_encode([ + 'auths' => [ + 'https://index.docker.io/v1/' => [ + 'username' => $username, + 'password' => $password, + 'email' => $email, + ], + ], + ])); + + $result = $this->call('/api/v1/namespaces/'.$this->namespace.'/pods', 'POST', json_encode([ + 'apiVersion' => 'v1', + 'kind' => 'Secret', + 'metadata' => [ + 'name' => 'utopia-orchestration-regcred', + 'namespace' => $this->namespace, + 'labels' => [ + 'app.kubernetes.io/managed-by' => 'utopia-php-orchestration', + ], + ], + 'data' => [ + '.dockerconfigjson' => $registryAuth, + ], + ]), [ + 'Content-Type: application/json', + 'Content-Length: '.\strlen(\json_encode($registryAuth)), + ]); + + if (! in_array($result['code'], [200, 201, 202])) { + throw new Orchestration('Failed to create regcred secret: '.$result['response'].' Response Code: '.$result['code']); + } else { + $this->regCred = true; + } + } + } + + /** + * @var string + */ + private $regCred = false; + + public function createNetwork(string $name, bool $internal = false): bool + { + return true; + } + + public function removeNetwork(string $name): bool + { + return true; + } + + public function networkConnect(string $container, string $network): bool + { + return true; + } + + public function networkDisconnect(string $container, string $network, bool $force = false): bool + { + return true; + } + + public function listNetworks(): array + { + return []; + } + + public function networkExists(string $name): bool + { + return true; + } + + public function pull(string $image): bool + { + return true; + } + + /** + * Create a request with cURL via the Kubernetes API + * + * @param array|bool|int|float|object|resource|string|null $body + * @param string[] $headers + * @return (bool|mixed|string)[] + * + * @psalm-return array{response: mixed, code: mixed} + */ + protected function call(string $url, string $method, $body = null, array $headers = [], int $timeout = -1): array + { + $url = 'https://'.System::getEnv('KUBERNETES_SERVICE_HOST', 'kubernetes.default.svc').'/'.$url; + $token = file_get_contents('/var/run/secrets/kubernetes.io/serviceaccount/token', false); + + array_push($headers, 'Authorization: Bearer '.$token); + + $ch = \curl_init(); + \curl_setopt($ch, CURLOPT_URL, $url); + \curl_setopt($ch, CURLOPT_SSL_VERIFYSTATUS, 0); + \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + \curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + + switch ($method) { + case 'GET': + if (! is_null($body)) { + \curl_setopt($ch, CURLOPT_URL, $url.'?'.$body); + } + break; + case 'POST': + \curl_setopt($ch, CURLOPT_POST, 1); + + if (! is_null($body)) { + \curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + break; + case 'DELETE': + \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + + if (! is_null($body)) { + \curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + break; + } + + $result = \curl_exec($ch); + $responseCode = \curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + + \curl_close($ch); + + return [ + 'response' => $result, + 'code' => $responseCode, + ]; + } + + /** + * Create a WebSocket request via the Kubernetes API + * + * @param string[] $headers + * @return (bool|mixed|string)[] + * + * @psalm-return array{response: mixed, code: mixed} + */ + public function callSocket(string $url, int $timeout = 60, array $headers = []): array + { + $url = 'wss://'.App::getEnv('KUBERNETES_SERVICE_HOST', 'kubernetes.default.svc').'/'.$url; + $token = file_get_contents('/var/run/secrets/kubernetes.io/serviceaccount/token', false); + + array_push($headers, 'Authorization: Bearer '.$token); + + $client = new WebSocket\Client($url, [ + 'headers' => $headers, + 'return_obj' => true, + 'timeout' => $timeout, + ]); + + $response = $client->receive(); + $client->close(); + + return [ + 'response' => $response->getContent(), + 'code' => $client->getCloseStatus(), + ]; + } + + /** + * Get usage stats of pods + * + * @param array $filters + * @return array + */ + public function getStats(string $pod = null, array $filters = []): array + { + // List ahead of time, since API does not allow listing all usage stats + $podIds = []; + + if ($pod === null) { + $pods = $this->list($filters); + $podIds = \array_map(fn ($c) => $c->getId(), $pods); + } else { + $podIds[] = $pod; + } + + $list = []; + + foreach ($podIds as $podId) { + $result = $this->call('/apis/metrics.k8s.io/v1beta1/namespaces/'.$this->namespace.'/pods/'.$podId, 'GET'); + + if ($result['code'] !== 200) { + throw new Orchestration($result['response']); + } + + $stats = \json_decode($result['response'], true); + + $list[] = new Stats( + podId: $podId, + containerName: current($stats)['name'], + cpuUsage: current($stats)['usage']['cpu'], + memoryUsage: current($stats)['usage']['memory'], + diskIO: ['in' => 0, 'out' => 0], // TODO: Implement (API does not provide these values) + memoryIO: ['in' => 0, 'out' => 0], // TODO: Implement (API does not provide these values + networkIO: ['in' => 0, 'out' => 0], // TODO: Implement (API does not provide these values) + ); + } + + return $list; + } + + /** + * List Pods + * + * @param array $filters + * @return Pod[] + */ + public function list(array $filters = []): array + { + $filtersSorted = []; + + foreach ($filters as $key => $value) { + $filtersSorted[$key] = [$value]; + } + + $body = [ + 'labelSelector' => empty($filtersSorted) ? new stdClass() : json_encode($filtersSorted), + ]; + + $result = $this->call('/api/v1/namespaces/'.$this->namespace.'/pods'.'?'.\http_build_query($body), 'GET'); + + $list = []; + + if ($result['code'] !== 200) { + throw new Orchestration($result['response']); + } + + foreach (\json_decode($result['response']['items'], true) as $value) { + + if (isset($value['spec']['containers'][0])) { + $parsedContainer = new Container( + $value['spec']['containers'][0]['name'], + $value['metadata']['name'], + $value['status'], + $value['metadata']['labels'] + ); + + array_push($list, $parsedContainer); + } + } + + return $list; + } + + /** + * Run Pod + * + * Creates and runs a new pod, On success it will return a string containing the pod name. + * On fail it will throw an exception. + * + * @param string $image + * @param string $name + * @param array $command + * @param string $entrypoint + * @param string $workdir + * @param array $volumes + * @param array $vars + * @param string $mountFolder + * @param array $labels + * @param string $hostname + * @param bool $remove + * @param string $network + * @param string $restart + */ + public function run( + string $image, string $name, array $command = [], string $entrypoint = '', string $workdir = '', array $volumes = [], array $vars = [], string $mountFolder = '', array $labels = [], string $hostname = '', bool $remove = false, string $network = '', string $restart = self::RESTART_NO): string { + $parsedVariables = []; + + foreach ($vars as $key => $value) { + array_push($parsedVariables, [ + 'name'=> $key, + 'value'=> $value, + ]); + } + + $vars = $parsedVariables; + + $body = [ + 'apiVersion' => 'v1', + 'kind' => 'Pod', + 'metadata' => [ + 'generateName' => $name, + 'namespace' => $this->namespace, + 'labels' => array_merge($labels, [ + 'app.kubernetes.io/component' => $name, + //'app.kubernetes.io/created-by' => $hostname, + 'app.kubernetes.io/managed-by' => 'utopia-php-orchestration', + ]), + 'annotations' => [], + ], + 'spec' => [ + 'containers' => [[ + 'imagePullSecrets' => ($this->regCred ? [[ + 'name' => 'utopia-orchestration-regcred', + ]] : []), + 'name' => $name, + 'image' => $image, + 'imagePullPolicy' => 'IfNotPresent', + 'command' => ($entrypoint ? [[$entrypoint]] : []), + 'args' => [$command], + 'workingDir' => $workdir, + 'env' => $vars, + ]], + ], + 'volumes' => $volumes, + 'restartPolicy' => 'Never', + 'hostname' => $hostname, + 'setHostnameAsFQDN' => true, + //'subdomain' => 'utopia', + 'resources' => [ + 'limits' => [ + 'cpu' => floatval($this->cpus), + 'memory' => intval($this->memory) * 1e+6, // Convert into bytes + ], + 'requests' => [ + 'cpu' => floatval($this->cpus), + 'memory' => intval($this->memory) * 1e+6, // Convert into bytes + ], + ], + ]; + + $body = array_filter($body, function ($value) { + return ! empty($value); + }); + + $result = $this->call('/api/v1/namespaces/'.$this->namespace.'/pods', 'POST', json_encode($body), [ + 'Content-Type: application/json', + 'Content-Length: '.\strlen(\json_encode($body)), + ]); + + if (! in_array($result['code'], [200, 201, 202])) { + throw new Orchestration('Failed to create function environment: '.$result['response'].' Response Code: '.$result['code']); + } + + $parsedResponse = json_decode($result['response'], true); + + return $parsedResponse['metadata']['name']; + } + + /** + * Execute Command in Pod + * + * @param string[] $command + * @param array $vars + */ + public function execute( + string $name, + array $command, + string &$output, + array $vars = [], + int $timeout = -1 + ): bool { + // TODO: need to solve to can change the environment variables + + $parsedCommand = []; + + foreach ($command as $value) { + array_push($parsedCommand, 'command='.$value); + } + + $result = $this->callSocket('/api/v1/namespaces/'.$this->namespace.'/pods/'.$name.'/exec?stderr=true&stdout=true&'.implode('&', $parsedCommand)); + + $output = $result['response']; + + if ($result['code'] !== 200) { + throw new Orchestration('Failed to create execute command: '.$result['response'].' Response Code: '.$result['code']); + } else { + return true; + } + } + + /** + * Remove Pod + */ + public function remove(string $name, bool $force = false): bool + { + $result = $this->call('/api/v1/namespaces/'.$this->namespace.'/pods/'.$name.($force ? '?gracePeriodSeconds=0' : ''), 'DELETE'); + + if ($result['code'] !== 200 || $result['code'] !== 202) { + throw new Orchestration('Failed to remove container: '.$result['response'].' Response Code: '.$result['code']); + } else { + return true; + } + } +}