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

Enhance Temporal Connection Configuration with SSL Support and Flexibility #83

Open
wants to merge 10 commits into
base: 3.x
Choose a base branch
from
22 changes: 19 additions & 3 deletions src/Bootloader/TemporalBridgeBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Spiral\RoadRunnerBridge\Bootloader\RoadRunnerBootloader;
use Spiral\TemporalBridge\Commands;
use Spiral\TemporalBridge\Config\TemporalConfig;
use Spiral\TemporalBridge\Connection\Connection;
use Spiral\TemporalBridge\Connection\SslConnection;
use Spiral\TemporalBridge\DeclarationLocator;
use Spiral\TemporalBridge\DeclarationLocatorInterface;
use Spiral\TemporalBridge\Dispatcher;
Expand Down Expand Up @@ -121,8 +123,10 @@ protected function initConfig(EnvironmentInterface $env): void
$this->config->setDefaults(
TemporalConfig::CONFIG,
[
'address' => $env->get('TEMPORAL_ADDRESS', '127.0.0.1:7233'),
'namespace' => 'App\\Endpoint\\Temporal\\Workflow',
'connection' => $env->get('TEMPORAL_CONNECTION', 'default'),
'connections' => [
'default' => new Connection(address: $env->get('TEMPORAL_ADDRESS', '127.0.0.1:7233')),
],
'defaultWorker' => (string)$env->get(
'TEMPORAL_TASK_QUEUE',
TemporalWorkerFactoryInterface::DEFAULT_TASK_QUEUE,
Expand Down Expand Up @@ -185,7 +189,19 @@ protected function initPipelineProvider(TemporalConfig $config, FactoryInterface

protected function initServiceClient(TemporalConfig $config): ServiceClientInterface
{
return ServiceClient::create($config->getAddress());
$connection = $config->getConnection($config->getDefaultConnection());

if ($connection instanceof SslConnection) {
return ServiceClient::createSSL(
address: $connection->address,
crt: (string) $connection->crt,
clientKey: $connection->clientKey,
clientPem: $connection->clientPem,
overrideServerName: $connection->overrideServerName,
);
}

return ServiceClient::create(address: $connection->address);
}

protected function initScheduleClient(
Expand Down
47 changes: 34 additions & 13 deletions src/Config/TemporalConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use Spiral\Core\Container\Autowire;
use Spiral\Core\InjectableConfig;
use Spiral\TemporalBridge\Connection\Connection;
use Spiral\TemporalBridge\Connection\DsnConnection;
use Spiral\TemporalBridge\Connection\SslConnection;
use Temporal\Client\ClientOptions;
use Temporal\Exception\ExceptionInterceptorInterface;
use Temporal\Internal\Interceptor\Interceptor;
Expand All @@ -21,8 +24,9 @@
* }
*
* @property array{
* address: non-empty-string,
* namespace: non-empty-string,
* address?: non-empty-string|null,
* connection: non-empty-string,
* connections: array<non-empty-string, Connection>,
* temporalNamespace: non-empty-string,
* defaultWorker: non-empty-string,
* workers: array<non-empty-string, WorkerOptions|TWorker>,
Expand All @@ -35,8 +39,8 @@
public const CONFIG = 'temporal';

protected array $config = [
'address' => 'localhost:7233',
'namespace' => 'App\\Endpoint\\Temporal\\Workflow',
'connection' => 'default',
'connections' => [],
'temporalNamespace' => 'default',
'defaultWorker' => WorkerFactoryInterface::DEFAULT_TASK_QUEUE,
'workers' => [],
Expand All @@ -47,25 +51,42 @@
/**
* @return non-empty-string
*/
public function getDefaultNamespace(): string
public function getTemporalNamespace(): string
{
return $this->config['namespace'];
return $this->config['temporalNamespace'];
}

/**
* @return non-empty-string
*/
public function getTemporalNamespace(): string
public function getDefaultConnection(): string
{
return $this->config['temporalNamespace'];
return $this->config['connection'] ?? 'default';
}

public function getConnection(string $name): Connection
{
// Legacy support. Will be removed in further versions.
// If you read this, please remove address from your configuration and use connections instead.
$address = $this->config['address'] ?? null;
if ($address !== null) {
\trigger_error(
'Using `address` is deprecated, use `connections` instead.',
\E_USER_DEPRECATED,
);
return new Connection(address: $address);
}

if (isset($this->config['connections'][$name])) {
return $this->config['connections'][$name];
}

throw new \InvalidArgumentException(\sprintf('Connection `%s` is not defined.', $name));
}

/**
* @return non-empty-string
* @deprecated
*/
public function getAddress(): string
{
return $this->config['address'];
return $this->getConnection($this->getDefaultConnection())->address;

Check warning on line 89 in src/Config/TemporalConfig.php

View check run for this annotation

Codecov / codecov/patch

src/Config/TemporalConfig.php#L89

Added line #L89 was not covered by tests
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/Connection/Connection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Spiral\TemporalBridge\Connection;

class Connection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class Connection
class InsecureConnection

{
public function __construct(
public readonly string $address,
butschster marked this conversation as resolved.
Show resolved Hide resolved
) {
}
}
25 changes: 25 additions & 0 deletions src/Connection/SslConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Spiral\TemporalBridge\Connection;

class SslConnection extends Connection
{
/**
* @param non-empty-string $address
* @param string $crt Full path to the certificate file (Default: null)
* @param string|null $clientKey Full path to the client key file (Default: null)
* @param string|null $clientPem Full path to the client pem file (Default: null)
butschster marked this conversation as resolved.
Show resolved Hide resolved
* @param string|null $overrideServerName
*/
public function __construct(
string $address,
public readonly ?string $crt = null,
public readonly ?string $clientKey = null,
public readonly ?string $clientPem = null,
public readonly ?string $overrideServerName = null,
) {
parent::__construct($address);
}
}
30 changes: 30 additions & 0 deletions src/Connection/TemporalCloudConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Spiral\TemporalBridge\Connection;

/**
* This connection is used to connect to Temporal Cloud.
*
* @see https://docs.temporal.io/cloud/get-started
*/
final class TemporalCloudConnection extends SslConnection
{
/**
* @param non-empty-string $address
* @param non-empty-string $clientKey Full path to the client key file. Required.
* @param non-empty-string $clientPem Full path to the client pem file. Required.
butschster marked this conversation as resolved.
Show resolved Hide resolved
*/
public function __construct(
string $address,
string $clientKey,
string $clientPem,
) {
parent::__construct(
address: $address,
clientKey: $clientKey,
clientPem: $clientPem,
);
}
}
28 changes: 28 additions & 0 deletions tests/app/config/temporal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

use Spiral\TemporalBridge\Connection\Connection;
use Spiral\TemporalBridge\Connection\SslConnection;
use Spiral\TemporalBridge\Connection\TemporalCloudConnection;

return [
'connection' => env('TEMPORAL_CONNECTION', 'default'),
'connections' => [
'default' => new Connection(
address: 'localhost:7233',
),
'ssl' => new SslConnection(
address: 'ssl:7233',
crt: '/path/to/crt',
clientKey: '/path/to/clientKey',
clientPem: '/path/to/clientPem',
overrideServerName: 'overrideServerName',
),
'temporal_cloud' => new TemporalCloudConnection(
address: 'ssl:7233',
clientKey: '/path/to/clientKey',
clientPem: '/path/to/clientPem',
),
],
];
7 changes: 0 additions & 7 deletions tests/app/src/config/temporal.php

This file was deleted.

45 changes: 44 additions & 1 deletion tests/src/Bootloader/TemporalBridgeBootloaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use Spiral\TemporalBridge\WorkerFactoryInterface;
use Spiral\TemporalBridge\WorkersRegistry;
use Spiral\TemporalBridge\WorkersRegistryInterface;
use Spiral\Testing\Attribute\Env;
use Temporal\Api\Workflowservice\V1\WorkflowServiceClient;
use Temporal\Client\GRPC\ServiceClient;
use Temporal\Client\GRPC\ServiceClientInterface;
use Temporal\Client\ScheduleClient;
Expand Down Expand Up @@ -63,7 +65,7 @@ public function testWorkerFactory(): void
{
$this->assertContainerBoundAsSingleton(
WorkerFactoryInterface::class,
WorkerFactory::class
WorkerFactory::class,
);
}

Expand Down Expand Up @@ -107,6 +109,47 @@ public function testPipelineProvider(): void
);
}

#[Env('TEMPORAL_CONNECTION', 'default')]
public function testConnection(): void
{
$client = $this->getContainer()->get(ServiceClientInterface::class);

$refl = new \ReflectionClass($client);
$baseClient = $refl->getParentClass();
$property = $baseClient->getProperty('workflowService');

$property->setAccessible(true);
/** @var WorkflowServiceClient $stub */
$stub = $property->getValue($client);

$this->assertSame('localhost:7233', $stub->getTarget());
}

#[Env('TEMPORAL_CONNECTION', 'ssl')]
public function testSslConnection(): void
{
$client = $this->getContainer()->get(ServiceClientInterface::class);

$refl = new \ReflectionClass($client);
$baseClient = $refl->getParentClass();
$property = $baseClient->getProperty('workflowService');

$property->setAccessible(true);
/** @var WorkflowServiceClient $stub */
$stub = $property->getValue($client);

$this->assertSame('ssl:7233', $stub->getTarget());
}

#[Env('TEMPORAL_CONNECTION', 'test')]
public function testNonExistsConnection(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Connection `test` is not defined.');

$this->getContainer()->get(ServiceClientInterface::class);
}

public function testAddWorkerOptions(): void
{
$configs = new ConfigManager($this->createMock(LoaderInterface::class));
Expand Down
Loading
Loading