Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.x] Exec API support #78

Merged
merged 9 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:

- name: Run Kubernetes Proxy
run: |
kubectl proxy --port=8080 &
kubectl proxy --port=8080 --reject-paths="^/non-existent-path" &

- name: Install dependencies
run: |
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"php": "^7.4|^8.0",
"guzzlehttp/guzzle": "^6.5|^7.0",
"illuminate/support": "^7.0|^8.0",
"ratchet/pawl": "^0.3.5",
"vierbergenlars/php-semver": "^2.1|^3.0"
},
"suggest": {
Expand Down
41 changes: 41 additions & 0 deletions docs/kinds/Pod.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- [Affinities & Anti-Affinities](#affinities--anti-affinities)
- [Container Retrieval](#container-retrieval)
- [Pod Logs](#pod-logs)
- [Pod Exec](#pod-exec)
- [Pod Status](#pod-status)
- [Containers' Statuses](#containers-statuses)

Expand Down Expand Up @@ -108,6 +109,46 @@ $pod->watchContainerLogs('mysql', function ($line) {
})
```

## Pod Exec

Commands can be executed within the Pod via the exec method. The result is the list of messages received prior to the WS being closed by the Kube API.

```php
$messages = $pod->exec(['/bin/sh', '-c', 'ls -al']);

foreach ($messages as $message) {
/**
[
"channel" => "stdout"
"message" => """
total 44\r\n
drwxr-xr-x 1 root root 4096 Mar 25 13:01 \e[1;34m.\e[m\r\n
drwxr-xr-x 1 root root 4096 Mar 25 13:01 \e[1;34m..\e[m\r\n
-rwxr-xr-x 1 root root 0 Mar 25 13:01 \e[1;32m.dockerenv\e[m\r\n
drwxr-xr-x 2 root root 12288 Mar 9 19:16 \e[1;34mbin\e[m\r\n
drwxr-xr-x 5 root root 360 Mar 25 13:01 \e[1;34mdev\e[m\r\n
drwxr-xr-x 1 root root 4096 Mar 25 13:01 \e[1;34metc\e[m\r\n
drwxr-xr-x 2 nobody nobody 4096 Mar 9 19:16 \e[1;34mhome\e[m\r\n
dr-xr-xr-x 226 root root 0 Mar 25 13:01 \e[1;34mproc\e[m\r\n
drwx------ 2 root root 4096 Mar 9 19:16 \e[1;34mroot\e[m\r\n
dr-xr-xr-x 12 root root 0 Mar 25 13:01 \e[1;34msys\e[m\r\n
drwxrwxrwt 2 root root 4096 Mar 9 19:16 \e[1;34mtmp\e[m\r\n
drwxr-xr-x 3 root root 4096 Mar 9 19:16 \e[1;34musr\e[m\r\n
drwxr-xr-x 1 root root 4096 Mar 25 13:01 \e[1;34mvar\e[m\r\n
"""
]
*/

echo "[{$message['channel']}] {$message['output']}".PHP_EOL;
}
```

Pass an additional container parameter in case there is more than just 1 container inside the pod:

```php
$messages = $pod->exec(['/bin/sh', '-c', 'ls -al'], 'mysql');
```

## Pod Status

The Status API is available to be accessed for fresh instances:
Expand Down
13 changes: 13 additions & 0 deletions src/Contracts/Executable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace RenokiCo\PhpK8s\Contracts;

interface Executable
{
/**
* Get the path, prefixed by '/', that points to the specific resource to exec.
*
* @return string
*/
public function resourceExecPath(): string;
}
8 changes: 8 additions & 0 deletions src/Exceptions/KubernetesExecException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace RenokiCo\PhpK8s\Exceptions;

class KubernetesExecException extends PhpK8sException
{
//
}
4 changes: 2 additions & 2 deletions src/Kinds/K8sPod.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace RenokiCo\PhpK8s\Kinds;

use Illuminate\Support\Str;
use RenokiCo\PhpK8s\Contracts\Dnsable;
use RenokiCo\PhpK8s\Contracts\Executable;
use RenokiCo\PhpK8s\Contracts\InteractsWithK8sCluster;
use RenokiCo\PhpK8s\Contracts\Loggable;
use RenokiCo\PhpK8s\Contracts\Watchable;
Expand All @@ -18,7 +18,7 @@
use RenokiCo\PhpK8s\Traits\HasStatusConditions;
use RenokiCo\PhpK8s\Traits\HasStatusPhase;

class K8sPod extends K8sResource implements Dnsable, InteractsWithK8sCluster, Watchable, Loggable
class K8sPod extends K8sResource implements Dnsable, Executable, InteractsWithK8sCluster, Watchable, Loggable
{
use HasAnnotations;
use HasLabels;
Expand Down
58 changes: 57 additions & 1 deletion src/Kinds/K8sResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Support\Str;
use RenokiCo\PhpK8s\Contracts\Executable;
use RenokiCo\PhpK8s\Contracts\Loggable;
use RenokiCo\PhpK8s\Contracts\Scalable;
use RenokiCo\PhpK8s\Contracts\Watchable;
use RenokiCo\PhpK8s\Exceptions\KubernetesAPIException;
use RenokiCo\PhpK8s\Exceptions\KubernetesExecException;
use RenokiCo\PhpK8s\Exceptions\KubernetesLogsException;
use RenokiCo\PhpK8s\Exceptions\KubernetesScalingException;
use RenokiCo\PhpK8s\Exceptions\KubernetesWatchException;
Expand Down Expand Up @@ -448,6 +450,7 @@ public function refreshOriginal(array $query = ['pretty' => 1])
*
* @param array $query
* @return \RenokiCo\PhpK8s\ResourcesList
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function all(array $query = ['pretty' => 1])
{
Expand All @@ -466,6 +469,7 @@ public function all(array $query = ['pretty' => 1])
*
* @param array $query
* @return \RenokiCo\PhpK8s\Kinds\K8sResource
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function get(array $query = ['pretty' => 1])
{
Expand All @@ -484,6 +488,7 @@ public function get(array $query = ['pretty' => 1])
*
* @param array $query
* @return \RenokiCo\PhpK8s\Kinds\K8sResource
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function create(array $query = ['pretty' => 1])
{
Expand All @@ -502,6 +507,7 @@ public function create(array $query = ['pretty' => 1])
*
* @param array $query
* @return bool
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function update(array $query = ['pretty' => 1]): bool
{
Expand Down Expand Up @@ -533,6 +539,7 @@ public function update(array $query = ['pretty' => 1]): bool
* @param null|int $gracePeriod
* @param string $propagationPolicy
* @return bool
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function delete(array $query = ['pretty' => 1], $gracePeriod = null, string $propagationPolicy = 'Foreground'): bool
{
Expand Down Expand Up @@ -650,11 +657,13 @@ public function watchByName(string $name, Closure $callback, array $query = ['pr
*
* @param array $query
* @return string
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesLogsException
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function logs(array $query = ['pretty' => 1])
{
if (! $this instanceof Loggable) {
throw new KubernetesWatchException(
throw new KubernetesLogsException(
'The resource '.get_class($this).' does not support logs.'
);
}
Expand All @@ -675,6 +684,8 @@ public function logs(array $query = ['pretty' => 1])
* @param string $container
* @param array $query
* @return string
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesLogsException
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function containerLogs(string $container, array $query = ['pretty' => 1])
{
Expand All @@ -688,6 +699,8 @@ public function containerLogs(string $container, array $query = ['pretty' => 1])
* @param Closure $callback
* @param array $query
* @return string
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesLogsException
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function logsByName(string $name, array $query = ['pretty' => 1])
{
Expand All @@ -702,6 +715,8 @@ public function logsByName(string $name, array $query = ['pretty' => 1])
* @param Closure $callback
* @param array $query
* @return string
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesLogsException
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function containerLogsByName(string $name, string $container, array $query = ['pretty' => 1])
{
Expand Down Expand Up @@ -766,6 +781,7 @@ public function watchContainerLogs(string $container, Closure $callback, array $
* @param array $query
* @return mixed
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesWatchException
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesLogsException
*/
public function watchLogsByName(string $name, Closure $callback, array $query = ['pretty' => 1])
{
Expand All @@ -792,6 +808,8 @@ public function watchContainerLogsByName(string $name, string $container, Closur
* Get a specific resource scaling data.
*
* @return \RenokiCo\PhpK8s\Kinds\K8sScale
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesScalingException
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function scaler(): K8sScale
{
Expand All @@ -815,6 +833,34 @@ public function scaler(): K8sScale
return $scaler;
}

/**
* Exec a command on the current resource.
*
* @param string|array $command
* @param string|null $container
* @param array $query
* @return string
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesExecException
* @throws \RenokiCo\PhpK8s\Exceptions\KubernetesAPIException
*/
public function exec($command, string $container = null, array $query = ['pretty' => 1, 'stdin' => 1, 'stdout' => 1, 'stderr' => 1, 'tty' => 1])
{
if (! $this instanceof Executable) {
throw new KubernetesExecException(
'The resource '.get_class($this).' does not support exec commands.'
);
}

return $this->cluster
->setResourceClass(get_class($this))
->runOperation(
KubernetesCluster::EXEC_OP,
$this->resourceExecPath(),
'',
['command' => $command, 'container' => $container] + $query
);
}

/**
* Get the path, prefixed by '/', that points to the resources list.
*
Expand Down Expand Up @@ -875,6 +921,16 @@ public function resourceLogPath(): string
return "{$this->getApiPathPrefix()}/".static::getPlural()."/{$this->getIdentifier()}/log";
}

/**
* Get the path, prefixed by '/', that points to the specific resource to exec.
*
* @return string
*/
public function resourceExecPath(): string
{
return "{$this->getApiPathPrefix()}/".static::getPlural()."/{$this->getIdentifier()}/exec";
}

/**
* Get the prefix path for the resource.
*
Expand Down
1 change: 1 addition & 0 deletions src/KubernetesCluster.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class KubernetesCluster
const LOG_OP = 'logs';
const WATCH_OP = 'watch';
const WATCH_LOGS_OP = 'watch_logs';
const EXEC_OP = 'exec';

/**
* Create a new class instance.
Expand Down
1 change: 1 addition & 0 deletions src/Traits/Cluster/LoadsFromKubeConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ protected function loadKubeConfigFromArray(array $kubeconfig, string $context):
* @param string $fileName
* @param string $contents
* @return string
* @throws \Exception
*/
protected function writeTempFileForContext(string $context, string $fileName, string $contents)
{
Expand Down
Loading