From 2d262aded032eabe30c37b2a34c6763732770649 Mon Sep 17 00:00:00 2001 From: butschster Date: Fri, 22 Mar 2024 10:30:39 +0400 Subject: [PATCH] Enhanced Temporal connection configuration for flexibility and SSL support This commit significantly overhauls the Temporal connection configuration system. The previous configuration was limited to specifying a single Temporal address with no support for SSL connections. With the new setup, developers can now define multiple connection options, including SSL-enabled connections. Changes include: - Introduction of a 'connections' array to specify multiple connection types (e.g., `default`, `SSL`). - Addition of the `SslConnection` class to handle SSL connection parameters such as certificates and keys. - Preservation of backward compatibility by supporting the previous `address` configuration under the `default` connection type. --- src/Bootloader/TemporalBridgeBootloader.php | 22 ++++++- src/Config/TemporalConfig.php | 45 +++++++++---- src/Connection/Connection.php | 23 +++++++ src/Connection/DsnConnection.php | 15 +++++ src/Connection/SslConnection.php | 25 +++++++ tests/app/config/temporal.php | 22 +++++++ tests/app/src/config/temporal.php | 7 -- .../TemporalBridgeBootloaderTest.php | 45 ++++++++++++- tests/src/Config/TemporalConfigTest.php | 65 +++++++++++-------- 9 files changed, 217 insertions(+), 52 deletions(-) create mode 100644 src/Connection/Connection.php create mode 100644 src/Connection/DsnConnection.php create mode 100644 src/Connection/SslConnection.php create mode 100644 tests/app/config/temporal.php delete mode 100644 tests/app/src/config/temporal.php diff --git a/src/Bootloader/TemporalBridgeBootloader.php b/src/Bootloader/TemporalBridgeBootloader.php index deecddd..81495cb 100644 --- a/src/Bootloader/TemporalBridgeBootloader.php +++ b/src/Bootloader/TemporalBridgeBootloader.php @@ -16,6 +16,8 @@ use Spiral\RoadRunnerBridge\Bootloader\RoadRunnerBootloader; use Spiral\TemporalBridge\Commands; use Spiral\TemporalBridge\Config\TemporalConfig; +use Spiral\TemporalBridge\Connection\SslConnection; +use Spiral\TemporalBridge\Connection\DsnConnection; use Spiral\TemporalBridge\DeclarationLocator; use Spiral\TemporalBridge\DeclarationLocatorInterface; use Spiral\TemporalBridge\Dispatcher; @@ -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 DsnConnection(address: $env->get('TEMPORAL_ADDRESS', '127.0.0.1:7233')), + ], 'defaultWorker' => (string)$env->get( 'TEMPORAL_TASK_QUEUE', TemporalWorkerFactoryInterface::DEFAULT_TASK_QUEUE, @@ -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->getAddress(), + crt: $connection->crt, + clientKey: $connection->clientKey, + clientPem: $connection->clientPem, + overrideServerName: $connection->overrideServerName, + ); + } + + return ServiceClient::create(address: $connection->getAddress()); } protected function initScheduleClient( diff --git a/src/Config/TemporalConfig.php b/src/Config/TemporalConfig.php index 9853921..04b7d63 100644 --- a/src/Config/TemporalConfig.php +++ b/src/Config/TemporalConfig.php @@ -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; @@ -21,8 +24,8 @@ * } * * @property array{ - * address: non-empty-string, - * namespace: non-empty-string, + * connection: non-empty-string, + * connections: array, * temporalNamespace: non-empty-string, * defaultWorker: non-empty-string, * workers: array, @@ -35,8 +38,8 @@ final class TemporalConfig extends InjectableConfig 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' => [], @@ -47,25 +50,41 @@ final class TemporalConfig extends InjectableConfig /** * @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 + { + if (isset($this->config['connections'][$name])) { + \assert( + $this->config['connections'][$name] instanceof Connection, + 'Connection must be an instance of Connection.', + ); + + return $this->config['connections'][$name]; + } + + + if ($this->config['connections'] === [] && $this->config['address'] !== null) { + return new DsnConnection($this->config['address']); + } + + 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())->getAddress(); } /** diff --git a/src/Connection/Connection.php b/src/Connection/Connection.php new file mode 100644 index 0000000..204b9f8 --- /dev/null +++ b/src/Connection/Connection.php @@ -0,0 +1,23 @@ +host . ':' . $this->port; + } +} diff --git a/src/Connection/DsnConnection.php b/src/Connection/DsnConnection.php new file mode 100644 index 0000000..78a428a --- /dev/null +++ b/src/Connection/DsnConnection.php @@ -0,0 +1,15 @@ + 'default', + 'connections' => [ + 'default' => new DsnConnection( + address: 'localhost:7233', + ), + 'ssl' => new SslConnection( + address: 'ssl:7233', + crt: '/path/to/crt', + clientKey: '/path/to/clientKey', + clientPem: '/path/to/clientPem', + overrideServerName: 'overrideServerName', + ), + ], +]; diff --git a/tests/app/src/config/temporal.php b/tests/app/src/config/temporal.php deleted file mode 100644 index 1d62656..0000000 --- a/tests/app/src/config/temporal.php +++ /dev/null @@ -1,7 +0,0 @@ - 'Spiral\\TemporalBridge\\Tests\\App\\Workflow', -]; diff --git a/tests/src/Bootloader/TemporalBridgeBootloaderTest.php b/tests/src/Bootloader/TemporalBridgeBootloaderTest.php index 5a4b7e7..16959f9 100644 --- a/tests/src/Bootloader/TemporalBridgeBootloaderTest.php +++ b/tests/src/Bootloader/TemporalBridgeBootloaderTest.php @@ -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; @@ -63,7 +65,7 @@ public function testWorkerFactory(): void { $this->assertContainerBoundAsSingleton( WorkerFactoryInterface::class, - WorkerFactory::class + WorkerFactory::class, ); } @@ -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)); diff --git a/tests/src/Config/TemporalConfigTest.php b/tests/src/Config/TemporalConfigTest.php index 67bf00a..c4b032d 100644 --- a/tests/src/Config/TemporalConfigTest.php +++ b/tests/src/Config/TemporalConfigTest.php @@ -5,6 +5,8 @@ namespace Spiral\TemporalBridge\Tests\Config; use Spiral\TemporalBridge\Config\TemporalConfig; +use Spiral\TemporalBridge\Connection\DsnConnection; +use Spiral\TemporalBridge\Connection\SslConnection; use Spiral\TemporalBridge\Tests\TestCase; use Temporal\Client\ClientOptions; use Temporal\Worker\WorkerFactoryInterface; @@ -12,22 +14,6 @@ final class TemporalConfigTest extends TestCase { - public function testGetsDefaultNamespace(): void - { - $config = new TemporalConfig([ - 'namespace' => 'foo' - ]); - - $this->assertSame('foo', $config->getDefaultNamespace()); - } - - public function testGetsDefaultNamespaceIfItNotSet(): void - { - $config = new TemporalConfig([]); - - $this->assertSame('App\\Endpoint\\Temporal\\Workflow', $config->getDefaultNamespace()); - } - public function testGetsDefaultTemporalNamespaceIfItNotSet(): void { $config = new TemporalConfig([]); @@ -44,26 +30,49 @@ public function testGetsDefaultTemporalNamespace(): void $this->assertSame('foo', $config->getTemporalNamespace()); } - public function testGetsAddress(): void + public function testGetConnectionFromAddress(): void { $config = new TemporalConfig([ - 'address' => 'localhost:1111' + 'address' => 'localhost:1111', ]); - $this->assertSame('localhost:1111', $config->getAddress()); + $connection = $config->getConnection('default'); + $this->assertSame(DsnConnection::class, $connection::class); + + $this->assertSame(1111, $connection->port); + $this->assertSame('localhost', $connection->host); } - public function testGetsAddressIfItNotSet(): void + public function testGetSslConnection(): void { - $config = new TemporalConfig([]); + $config = new TemporalConfig([ + 'connections' => [ + 'default' => new SslConnection( + address: 'localhost:2222', + crt: 'crt', + clientKey: 'clientKey', + clientPem: 'clientPem', + overrideServerName: 'overrideServerName', + ), + ], + ]); + + $connection = $config->getConnection('default'); + + $this->assertSame(SslConnection::class, $connection::class); - $this->assertSame('localhost:7233', $config->getAddress()); + $this->assertSame(2222, $connection->port); + $this->assertSame('localhost', $connection->host); + $this->assertSame('crt', $connection->crt); + $this->assertSame('clientKey', $connection->clientKey); + $this->assertSame('clientPem', $connection->clientPem); + $this->assertSame('overrideServerName', $connection->overrideServerName); } public function testGetsDefaultWorker(): void { $config = new TemporalConfig([ - 'defaultWorker' => 'some-worker' + 'defaultWorker' => 'some-worker', ]); $this->assertSame('some-worker', $config->getDefaultWorker()); @@ -86,23 +95,23 @@ public function testGetsWorkers(): void ], 'withInterceptors' => [ 'interceptors' => [ - 'foo' + 'foo', ], ], 'withExceptionInterceptor' => [ - 'exception_interceptor' => 'bar' + 'exception_interceptor' => 'bar', ], 'all' => [ 'options' => WorkerOptions::new(), 'interceptors' => [ - 'foo' + 'foo', ], - 'exception_interceptor' => 'bar' + 'exception_interceptor' => 'bar', ], ]; $config = new TemporalConfig([ - 'workers' => $workers + 'workers' => $workers, ]); $this->assertSame($workers, $config->getWorkers());