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

Merged
merged 20 commits into from
Aug 30, 2024
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
3 changes: 3 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# These are supported funding model platforms

github: spiral
22 changes: 11 additions & 11 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@
"chat": "https://discord.gg/V6EK4he"
},
"license": "MIT",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/spiral"
}
],
"authors": [
{
"name": "Anton Titov (wolfy-j)",
"email": "[email protected]"
},
{
"name": "Pavel Butchnev (butschster)",
"name": "Pavel Buchnev (butschster)",
"email": "[email protected]"
},
{
Expand All @@ -38,15 +44,15 @@
"require": {
"php": "^8.1",
"spiral/boot": "^3.13",
"spiral/attributes": "^2.8 || ^3.0",
"spiral/attributes": "^2.8 || ^3.1.5",
"spiral/tokenizer": "^3.13",
"spiral/scaffolder": "^3.13",
"spiral/roadrunner-bridge": "^2.0 || ^3.5",
"temporal/sdk": "^2.7"
"spiral/roadrunner-bridge": "^2.0 || ^3.6",
"temporal/sdk": "^2.10"
},
"require-dev": {
"spiral/framework": "^3.13",
"spiral/testing": "^2.7",
"spiral/testing": "^2.8",
"vimeo/psalm": "^5.23"
},
"autoload": {
Expand All @@ -60,12 +66,6 @@
"Spiral\\TemporalBridge\\Tests\\": "tests/src"
}
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/roadrunner-server"
}
],
"scripts": {
"test": "vendor/bin/phpunit",
"psalm": "vendor/bin/psalm --no-cache --config=psalm.xml ./src"
Expand Down
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</projectFiles>
<issueHandlers>
<MissingClassConstType errorLevel="suppress" />
<DeprecatedMethod errorLevel="suppress" />
<DeprecatedInterface>
<errorLevel type="suppress">
<file name="src/Commands/InfoCommand.php"/>
Expand Down
35 changes: 29 additions & 6 deletions src/Bootloader/TemporalBridgeBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
use Spiral\Core\FactoryInterface;
use Spiral\RoadRunnerBridge\Bootloader\RoadRunnerBootloader;
use Spiral\TemporalBridge\Commands;
use Spiral\TemporalBridge\Config\ClientConfig;
use Spiral\TemporalBridge\Config\ConnectionConfig;
use Spiral\TemporalBridge\Config\TemporalConfig;
use Spiral\TemporalBridge\DeclarationLocator;
use Spiral\TemporalBridge\DeclarationLocatorInterface;
Expand Down Expand Up @@ -95,9 +97,7 @@ public function defineSingletons(): array

DataConverterInterface::class => static fn() => DataConverter::createDefault(),
PipelineProvider::class => [self::class, 'initPipelineProvider'],
ServiceClientInterface::class => static fn(
TemporalConfig $config,
): ServiceClientInterface => ServiceClient::create($config->getAddress()),
ServiceClientInterface::class => [self::class, 'initServiceClient'],
];
}

Expand Down Expand Up @@ -156,18 +156,41 @@ 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',
'client' => $env->get('TEMPORAL_CONNECTION', 'default'),
'clients' => [
'default' => ClientConfig::new(
ConnectionConfig::new(
address: $env->get('TEMPORAL_ADDRESS', '127.0.0.1:7233'),
),
),
],
'defaultWorker' => (string)$env->get(
'TEMPORAL_TASK_QUEUE',
TemporalWorkerFactoryInterface::DEFAULT_TASK_QUEUE,
),
'workers' => [],
'clientOptions' => null,
],
);
}

protected function initServiceClient(TemporalConfig $config): ServiceClientInterface
{
$client = $config->getClientConfig($config->getDefaultClient());
$connection = $client->connection;

$result = $connection->isSecure()
? ServiceClient::createSSL(
address: $connection->address,
crt: $connection->tlsConfig->rootCerts,
clientKey: $connection->tlsConfig->privateKey,
clientPem: $connection->tlsConfig->certChain,
overrideServerName: $connection->tlsConfig->serverName,
)
: ServiceClient::create(address: $connection->address);

return $result->withContext($client->context);
}

protected function initPipelineProvider(TemporalConfig $config, FactoryInterface $factory): PipelineProvider
{
/** @var Interceptor[] $interceptors */
Expand Down
60 changes: 60 additions & 0 deletions src/Config/ClientConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Spiral\TemporalBridge\Config;

use Temporal\Client\ClientOptions;
use Temporal\Client\GRPC\Context;
use Temporal\Client\GRPC\ContextInterface;

/**
* Temporal Client configuration.
*
* ClientConfig::new(
* ConnectionConfig::new('localhost:7233')
* ->withTls(
* privateKey: '/my-project.key',
* certChain: '/my-project.pem',
* ),
* (new ClientOptions())
* ->withNamespace('default'),
* Context::default()
* ->withTimeout(4.5)
* ->withRetryOptions(
* RpcRetryOptions::new()
* ->withMaximumAttempts(5)
* ->withInitialInterval(3)
* ->withMaximumInterval(10)
* ->withBackoffCoefficient(1.6)
* ),
* ),
* ),
*/
final class ClientConfig
{
private function __construct(
public readonly ConnectionConfig $connection,
public readonly ClientOptions $options,
public readonly ContextInterface $context,
) {}

/**
* Create a new client configuration.
*
* @param ConnectionConfig $connection
* @param ClientOptions|null $options
* @param ContextInterface|null $context Default Service Client context.
*/
public static function new(
ConnectionConfig $connection,
?ClientOptions $options = null,
?ContextInterface $context = null,
): self {
return new self(
$connection,
$options ?? new ClientOptions(),
$context ?? Context::default(),
);
}
}
93 changes: 93 additions & 0 deletions src/Config/ConnectionConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace Spiral\TemporalBridge\Config;

/**
* Temporal connection and credentials configuration.
*
* How to connect to local Temporal server:
*
* ConnectionConfig::new('localhost:7233'),
*
* How to connect to Temporal Cloud:
*
* ConnectionConfig::new('foo-bar-default.baz.tmprl.cloud:7233')
* ->withTls(
* privateKey: '/my-project.key',
* certChain: '/my-project.pem',
* ),
*/
final class ConnectionConfig
{
/**
* @param non-empty-string $address
* @param non-empty-string|\Stringable|null $authToken
*/
private function __construct(
public readonly string $address,
public readonly ?TlsConfig $tlsConfig = null,
public readonly string|\Stringable|null $authToken = null,
) {}

/**
* Check if the connection is secure.
*
* @psalm-assert-if-true TlsConfig $this->tlsConfig
* @psalm-assert-if-false null $this->tlsConfig
*/
public function isSecure(): bool
{
return $this->tlsConfig !== null;
}

/**
* @param non-empty-string $address
*/
public static function new(
string $address,
): self {
return new self($address);
}

/**
* Set the TLS configuration for the connection.
*
* @param non-empty-string|null $rootCerts Root certificates string or file in PEM format.
* If null provided, default gRPC root certificates are used.
* @param non-empty-string|null $privateKey Client private key string or file in PEM format.
* @param non-empty-string|null $certChain Client certificate chain string or file in PEM format.
* @param non-empty-string|null $serverName Server name override for TLS verification.
*/
public function withTls(
?string $rootCerts = null,
?string $privateKey = null,
?string $certChain = null,
?string $serverName = null,
): self {
return new self(
$this->address,
new TlsConfig($rootCerts, $privateKey, $certChain, $serverName),
);
}

/**
* Set the authentication token for the service client.
*
* This is the equivalent of providing an "Authorization" header with "Bearer " + the given key.
* This will overwrite any "Authorization" header that may be on the context before each request to the
* Temporal service.
* You may pass your own {@see \Stringable} implementation to be able to change the key dynamically.
*
* @param non-empty-string|\Stringable|null $authToken
*/
public function withAuthKey(string|\Stringable|null $authToken): self
{
return new self(
$this->address,
$this->tlsConfig,
$authToken,
);
}
}
Loading
Loading