Skip to content
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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ jobs:
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --${{ matrix.prefer }} --prefer-dist --no-interaction --no-suggest

- name: Setup in-cluster config
run: |
sudo mkdir -p /var/run/secrets/kubernetes.io/serviceaccount
echo "some-token" | sudo tee /var/run/secrets/kubernetes.io/serviceaccount/token
echo "c29tZS1jZXJ0Cg==" | sudo tee /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
echo "some-namespace" | sudo tee /var/run/secrets/kubernetes.io/serviceaccount/namespace
sudo chmod -R 777 /var/run/secrets/kubernetes.io/serviceaccount/

- name: Run tests
run: |
vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
Expand Down
18 changes: 18 additions & 0 deletions docs/Cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,21 @@ For testing purposes or local checkups, you can disable SSL checks:
```php
$cluster->withoutSslChecks();
```

## In-Cluster Configuration

Kubernetes allows Pods to access [the internal kubeapi within a container](https://kubernetes.io/docs/tasks/run-application/access-api-from-pod/).

PhpK8s allows you to set up an in-cluster-ready client with minimal configuration. Please keep in mind that this works only within pods that run in a Kubernetes cluster.

```php
use RenokiCo\PhpK8s\KubernetesCluster;

$cluster = new KubernetesCluster('https://kubernetes.default.svc');

$cluster->inClusterConfiguration();

foreach ($cluster->getAllServices() as $svc) {
//
}
```
7 changes: 0 additions & 7 deletions src/KubernetesCluster.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@ class KubernetesCluster
*/
protected $url;

/**
* The API port.
*
* @var int
*/
protected $port = 8080;

/**
* The class name for the K8s resource.
*
Expand Down
39 changes: 38 additions & 1 deletion src/Traits/Cluster/AuthenticatesCluster.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace RenokiCo\PhpK8s\Traits\Cluster;

use RenokiCo\PhpK8s\Kinds\K8sResource;

trait AuthenticatesCluster
{
/**
Expand Down Expand Up @@ -52,7 +54,7 @@ trait AuthenticatesCluster
*/
public function withToken(string $token = null)
{
$this->token = str_replace(["\r", "\n"], '', $token);
$this->token = $this->normalize($token);

return $this;
}
Expand Down Expand Up @@ -134,4 +136,39 @@ public function withoutSslChecks()

return $this;
}

/**
* Load the in-cluster configuration to run the code
* under a Pod in a cluster.
*
* @return $this
*/
public function inClusterConfiguration()
{
if (file_exists($tokenPath = '/var/run/secrets/kubernetes.io/serviceaccount/token')) {
$this->loadTokenFromFile($tokenPath);
}

if (file_exists($caPath = '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt')) {
$this->withCaCertificate($caPath);
}

if ($namespace = @file_get_contents('/var/run/secrets/kubernetes.io/serviceaccount/namespace')) {
K8sResource::setDefaultNamespace($this->normalize($namespace));
}

return $this;
}

/**
* Replace \r and \n with nothing. Used to read
* strings from files that might contain extra chars.
*
* @param string $content
* @return string
*/
protected function normalize(string $content): string
{
return str_replace(["\r", "\n"], '', $content);
}
}
55 changes: 44 additions & 11 deletions tests/KubeConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use RenokiCo\PhpK8s\Exceptions\KubeConfigClusterNotFound;
use RenokiCo\PhpK8s\Exceptions\KubeConfigContextNotFound;
use RenokiCo\PhpK8s\Exceptions\KubeConfigUserNotFound;
use RenokiCo\PhpK8s\Kinds\K8sResource;
use RenokiCo\PhpK8s\KubernetesCluster;

class KubeConfigTest extends TestCase
Expand All @@ -21,13 +22,15 @@ public function setUp(): void

public function test_kube_config_from_yaml_file_with_base64_encoded_ssl()
{
$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube');
$cluster = new KubernetesCluster('http://127.0.0.1:8080');

$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube');

[
'verify' => $caPath,
'cert' => $certPath,
'ssl_key' => $keyPath,
] = $this->cluster->getClient()->getConfig();
] = $cluster->getClient()->getConfig();

$this->assertEquals("some-ca\n", file_get_contents($caPath));
$this->assertEquals("some-cert\n", file_get_contents($certPath));
Expand All @@ -36,13 +39,15 @@ public function test_kube_config_from_yaml_file_with_base64_encoded_ssl()

public function test_kube_config_from_yaml_file_with_paths_to_ssl()
{
$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-2');
$cluster = new KubernetesCluster('http://127.0.0.1:8080');

$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-2');

[
'verify' => $caPath,
'cert' => $certPath,
'ssl_key' => $keyPath,
] = $this->cluster->getClient()->getConfig();
] = $cluster->getClient()->getConfig();

$this->assertEquals('/path/to/.minikube/ca.crt', $caPath);
$this->assertEquals('/path/to/.minikube/client.crt', $certPath);
Expand All @@ -51,40 +56,68 @@ public function test_kube_config_from_yaml_file_with_paths_to_ssl()

public function test_kube_config_from_yaml_cannot_load_if_no_cluster()
{
$cluster = new KubernetesCluster('http://127.0.0.1:8080');

$this->expectException(KubeConfigClusterNotFound::class);

$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-without-cluster');
$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-without-cluster');
}

public function test_kube_config_from_yaml_cannot_load_if_no_user()
{
$cluster = new KubernetesCluster('http://127.0.0.1:8080');

$this->expectException(KubeConfigUserNotFound::class);

$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-without-user');
$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-without-user');
}

public function test_kube_config_from_yaml_cannot_load_if_wrong_context()
{
$cluster = new KubernetesCluster('http://127.0.0.1:8080');

$this->expectException(KubeConfigContextNotFound::class);

$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'inexistent-context');
$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'inexistent-context');
}

public function test_http_authentication()
{
$this->cluster->httpAuthentication('some-user', 'some-password');
$cluster = new KubernetesCluster('http://127.0.0.1:8080');

['auth' => $auth] = $this->cluster->getClient()->getConfig();
$cluster->httpAuthentication('some-user', 'some-password');

['auth' => $auth] = $cluster->getClient()->getConfig();

$this->assertEquals(['some-user', 'some-password'], $auth);
}

public function test_bearer_token_authentication()
{
$this->cluster->loadTokenFromFile(__DIR__.'/cluster/token.txt');
$cluster = new KubernetesCluster('http://127.0.0.1:8080');

$cluster->loadTokenFromFile(__DIR__.'/cluster/token.txt');

['headers' => ['authorization' => $token]] = $this->cluster->getClient()->getConfig();
['headers' => ['authorization' => $token]] = $cluster->getClient()->getConfig();

$this->assertEquals('Bearer some-token', $token);
}

public function test_in_cluster_config()
{
$cluster = new KubernetesCluster('http://127.0.0.1:8080');

$cluster->inClusterConfiguration();

[
'headers' => ['authorization' => $token],
'verify' => $caPath,
] = $cluster->getClient()->getConfig();

$this->assertEquals('Bearer some-token', $token);
$this->assertEquals('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt', $caPath);
$this->assertEquals('some-namespace', K8sResource::$defaultNamespace);

K8sResource::setDefaultNamespace('default');
}
}