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

Expose API key client option #418

Merged
merged 2 commits into from
Apr 12, 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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"psr/log": "^2.0 || ^3.0",
"ramsey/uuid": "^4.7",
"react/promise": "^2.9",
"roadrunner-php/roadrunner-api-dto": "^1.5.0",
"roadrunner-php/roadrunner-api-dto": "^1.7.0",
"roadrunner-php/version-checker": "^1.0",
"spiral/attributes": "^3.1.4",
"spiral/roadrunner": "^2023.3.12 || ^2024.1",
Expand Down
8 changes: 8 additions & 0 deletions resources/scripts/generate-client.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@
);
$m->setReturnType('static');
$interface->addMethodFromGenerator($m);
// withAuthKey(string $key): static
$m = new MethodGenerator(
'withAuthKey',
[Generator\ParameterGenerator::fromArray(['type' => '\Stringable|string', 'name' => 'key'])],
MethodGenerator::FLAG_PUBLIC,
);
$m->setReturnType('static');
$interface->addMethodFromGenerator($m);
// public function getConnection(): ConnectionInterface
$m = new MethodGenerator(
'getConnection',
Expand Down
3 changes: 3 additions & 0 deletions src/Client/Common/ServerCapabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace Temporal\Client\Common;

/**
* @see \Temporal\Api\Workflowservice\V1\GetSystemInfoResponse\Capabilities
*/
final class ServerCapabilities
{
/**
Expand Down
30 changes: 27 additions & 3 deletions src/Client/GRPC/BaseClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ abstract class BaseClient implements ServiceClientInterface

private Connection $connection;
private ContextInterface $context;
private \Stringable|string $apiKey = '';

/**
* @param WorkflowServiceClient|Closure(): WorkflowServiceClient $workflowService Service Client or its factory
Expand Down Expand Up @@ -74,6 +75,21 @@ public function withContext(ContextInterface $context): static
return $clone;
}

/**
* 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.
*/
public function withAuthKey(\Stringable|string $key): static
{
$clone = clone $this;
$clone->apiKey = $key;
return $clone;
}

/**
* Close the communication channel associated with this stub.
*/
Expand Down Expand Up @@ -157,8 +173,8 @@ final public function withInterceptorPipeline(?Pipeline $pipeline): static
{
$clone = clone $this;
/** @see GrpcClientInterceptor::interceptCall() */
$callable = $pipeline?->with(Closure::fromCallable([$clone, 'call']), 'interceptCall');
$clone->invokePipeline = $callable === null ? null : Closure::fromCallable($callable);
$callable = $pipeline?->with($clone->call(...), 'interceptCall');
$clone->invokePipeline = $callable === null ? null : $callable(...);
return $clone;
}

Expand Down Expand Up @@ -224,10 +240,18 @@ public function getConnection(): ConnectionInterface {
*
* @throw ClientException
*/
protected function invoke(string $method, object $arg, ?ContextInterface $ctx = null)
protected function invoke(string $method, object $arg, ?ContextInterface $ctx = null): mixed
{
$ctx ??= $this->context;

// Add the API key to the context
$key = (string)$this->apiKey;
if ($key !== '') {
$ctx = $ctx->withMetadata([
'Authorization' => ["Bearer $key"],
]);
}

return $this->invokePipeline !== null
? ($this->invokePipeline)($method, $arg, $ctx)
: $this->call($method, $arg, $ctx);
Expand Down
44 changes: 43 additions & 1 deletion src/Client/GRPC/ServiceClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,11 @@ public function DescribeWorkflowExecution(V1\DescribeWorkflowExecutionRequest $a
}

/**
* DescribeTaskQueue returns information about the target task queue.
* DescribeTaskQueue returns the following information about the target task queue,
* broken down by Build ID:
* - List of pollers
* - Workflow Reachability status
* - Backlog info for Workflow and/or Activity tasks
*
* @param V1\DescribeTaskQueueRequest $arg
* @param ContextInterface|null $ctx
Expand Down Expand Up @@ -873,6 +877,8 @@ public function ListSchedules(V1\ListSchedulesRequest $arg, ContextInterface $ct
}

/**
* Deprecated. Use `UpdateWorkerVersioningRules`.
*
* Allows users to specify sets of worker build id versions on a per task queue
* basis. Versions
* are ordered, and may be either compatible with some extant version, or a new
Expand Down Expand Up @@ -906,6 +912,7 @@ public function UpdateWorkerBuildIdCompatibility(V1\UpdateWorkerBuildIdCompatibi
}

/**
* Deprecated. Use `GetWorkerVersioningRules`.
* Fetches the worker build id versioning sets for a task queue.
*
* @param V1\GetWorkerBuildIdCompatibilityRequest $arg
Expand All @@ -919,6 +926,41 @@ public function GetWorkerBuildIdCompatibility(V1\GetWorkerBuildIdCompatibilityRe
}

/**
* Allows updating the Build ID assignment and redirect rules for a given Task
* Queue.
* WARNING: Worker Versioning is not yet stable and the API and behavior may change
* incompatibly.
* (-- api-linter: core::0127::http-annotation=disabled
* aip.dev/not-precedent: We do yet expose versioning API to HTTP. --)
*
* @param V1\UpdateWorkerVersioningRulesRequest $arg
* @param ContextInterface|null $ctx
* @return V1\UpdateWorkerVersioningRulesResponse
* @throws ServiceClientException
*/
public function UpdateWorkerVersioningRules(V1\UpdateWorkerVersioningRulesRequest $arg, ContextInterface $ctx = null) : V1\UpdateWorkerVersioningRulesResponse
{
return $this->invoke("UpdateWorkerVersioningRules", $arg, $ctx);
}

/**
* Fetches the Build ID assignment and redirect rules for a Task Queue.
* WARNING: Worker Versioning is not yet stable and the API and behavior may change
* incompatibly.
*
* @param V1\GetWorkerVersioningRulesRequest $arg
* @param ContextInterface|null $ctx
* @return V1\GetWorkerVersioningRulesResponse
* @throws ServiceClientException
*/
public function GetWorkerVersioningRules(V1\GetWorkerVersioningRulesRequest $arg, ContextInterface $ctx = null) : V1\GetWorkerVersioningRulesResponse
{
return $this->invoke("GetWorkerVersioningRules", $arg, $ctx);
}

/**
* Deprecated. Use `DescribeTaskQueue`.
*
* Fetches task reachability to determine whether a worker may be retired.
* The request may specify task queues to query for or let the server fetch all
* task queues mapped to the given
Expand Down
37 changes: 36 additions & 1 deletion src/Client/GRPC/ServiceClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface ServiceClientInterface
{
public function getContext() : ContextInterface;
public function withContext(ContextInterface $context) : static;
public function withAuthKey(\Stringable|string $key) : static;
public function getConnection() : \Temporal\Client\GRPC\Connection\ConnectionInterface;
public function getServerCapabilities() : ?\Temporal\Client\Common\ServerCapabilities;
/**
Expand Down Expand Up @@ -585,7 +586,11 @@ public function QueryWorkflow(V1\QueryWorkflowRequest $arg, ContextInterface $ct
*/
public function DescribeWorkflowExecution(V1\DescribeWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\DescribeWorkflowExecutionResponse;
/**
* DescribeTaskQueue returns information about the target task queue.
* DescribeTaskQueue returns the following information about the target task queue,
* broken down by Build ID:
* - List of pollers
* - Workflow Reachability status
* - Backlog info for Workflow and/or Activity tasks
*
* @param V1\DescribeTaskQueueRequest $arg
* @param ContextInterface|null $ctx
Expand Down Expand Up @@ -685,6 +690,8 @@ public function DeleteSchedule(V1\DeleteScheduleRequest $arg, ContextInterface $
*/
public function ListSchedules(V1\ListSchedulesRequest $arg, ContextInterface $ctx = null) : V1\ListSchedulesResponse;
/**
* Deprecated. Use `UpdateWorkerVersioningRules`.
*
* Allows users to specify sets of worker build id versions on a per task queue
* basis. Versions
* are ordered, and may be either compatible with some extant version, or a new
Expand Down Expand Up @@ -714,6 +721,7 @@ public function ListSchedules(V1\ListSchedulesRequest $arg, ContextInterface $ct
*/
public function UpdateWorkerBuildIdCompatibility(V1\UpdateWorkerBuildIdCompatibilityRequest $arg, ContextInterface $ctx = null) : V1\UpdateWorkerBuildIdCompatibilityResponse;
/**
* Deprecated. Use `GetWorkerVersioningRules`.
* Fetches the worker build id versioning sets for a task queue.
*
* @param V1\GetWorkerBuildIdCompatibilityRequest $arg
Expand All @@ -723,6 +731,33 @@ public function UpdateWorkerBuildIdCompatibility(V1\UpdateWorkerBuildIdCompatibi
*/
public function GetWorkerBuildIdCompatibility(V1\GetWorkerBuildIdCompatibilityRequest $arg, ContextInterface $ctx = null) : V1\GetWorkerBuildIdCompatibilityResponse;
/**
* Allows updating the Build ID assignment and redirect rules for a given Task
* Queue.
* WARNING: Worker Versioning is not yet stable and the API and behavior may change
* incompatibly.
* (-- api-linter: core::0127::http-annotation=disabled
* aip.dev/not-precedent: We do yet expose versioning API to HTTP. --)
*
* @param V1\UpdateWorkerVersioningRulesRequest $arg
* @param ContextInterface|null $ctx
* @return V1\UpdateWorkerVersioningRulesResponse
* @throws ServiceClientException
*/
public function UpdateWorkerVersioningRules(V1\UpdateWorkerVersioningRulesRequest $arg, ContextInterface $ctx = null) : V1\UpdateWorkerVersioningRulesResponse;
/**
* Fetches the Build ID assignment and redirect rules for a Task Queue.
* WARNING: Worker Versioning is not yet stable and the API and behavior may change
* incompatibly.
*
* @param V1\GetWorkerVersioningRulesRequest $arg
* @param ContextInterface|null $ctx
* @return V1\GetWorkerVersioningRulesResponse
* @throws ServiceClientException
*/
public function GetWorkerVersioningRules(V1\GetWorkerVersioningRulesRequest $arg, ContextInterface $ctx = null) : V1\GetWorkerVersioningRulesResponse;
/**
* Deprecated. Use `DescribeTaskQueue`.
*
* Fetches task reachability to determine whether a worker may be retired.
* The request may specify task queues to query for or let the server fetch all
* task queues mapped to the given
Expand Down
1 change: 1 addition & 0 deletions src/Common/WorkerVersionStamp.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ final class WorkerVersionStamp
{
public function __construct(
public string $buildId = '',
/** @deprecated that field was removed {@link https://github.com/temporalio/api/pull/393} */
public string $bundleId = '',
public bool $useVersioning = false,
) {
Expand Down
1 change: 0 additions & 1 deletion src/Internal/Mapper/WorkflowExecutionInfoMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public function prepareWorkerVersionStamp(?WorkerVersionStamp $versionStamp): ?W
? null
: new WorkerVersionStampDto(
buildId: $versionStamp->getBuildId(),
bundleId: $versionStamp->getBundleId(),
useVersioning: $versionStamp->getUseVersioning(),
);
}
Expand Down
67 changes: 65 additions & 2 deletions tests/Unit/Client/GRPC/BaseClientTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Temporal\Client\GRPC\Connection\ConnectionState;
use Temporal\Client\GRPC\ContextInterface;
use Temporal\Client\GRPC\ServiceClient;
use Temporal\Internal\Interceptor\Pipeline;

class BaseClientTestCase extends TestCase
{
Expand Down Expand Up @@ -80,9 +81,55 @@ public function testWithContext(): void
$this->assertNotSame($client, $client2);
}

public function testWithAuthKey(): void
{
$client = $this->createClientMock();
$context = $client->getContext();
$client2 = $client->withAuthKey('test-key');

// Client immutability
$this->assertNotSame($client, $client2);
// Old context was not modified
$this->assertSame($context, $client->getContext());
// New context is the same as the old one
// because the auth key is added to the context before API method call
$this->assertSame($context, $client2->getContext());

$ctx1 = $client->testCall()->ctx;
self::assertInstanceOf(ContextInterface::class, $ctx1);
$this->assertArrayNotHasKey('Authorization', $ctx1->getMetadata());

$ctx2 = $client2->testCall()->ctx;
self::assertInstanceOf(ContextInterface::class, $ctx2);
$this->assertArrayHasKey('Authorization', $ctx2->getMetadata());
$this->assertSame(['Bearer test-key'], $ctx2->getMetadata()['Authorization']);
}

public function testWithDynamicAuthKey(): void
{
$client = $this->createClientMock()->withAuthKey(new class implements \Stringable {
public function __toString(): string
{
static $counter = 0;
$counter++;
return "test-key-$counter";
}
});

$ctx = $client->testCall()->ctx;
self::assertInstanceOf(ContextInterface::class, $ctx);
$this->assertArrayHasKey('Authorization', $ctx->getMetadata());
$this->assertSame(['Bearer test-key-1'], $ctx->getMetadata()['Authorization']);

$ctx2 = $client->testCall()->ctx;
self::assertInstanceOf(ContextInterface::class, $ctx2);
$this->assertArrayHasKey('Authorization', $ctx2->getMetadata());
$this->assertSame(['Bearer test-key-2'], $ctx2->getMetadata()['Authorization']);
}

private function createClientMock(?callable $serviceClientFactory = null): BaseClient
{
return new class($serviceClientFactory ?? static fn() => new class extends WorkflowServiceClient {
return (new class($serviceClientFactory ?? static fn() => new class extends WorkflowServiceClient {
public function __construct()
{
}
Expand All @@ -104,6 +151,22 @@ public function getSystemInfo(
->setCapabilities((new Capabilities)->setSupportsSchedules(true))
->setServerVersion('1.2.3');
}
};

public function testCall(): mixed
{
return $this->invoke("testCall", (object)[], null);
}
})->withInterceptorPipeline(
Pipeline::prepare([new class implements \Temporal\Interceptor\GrpcClientInterceptor {
public function interceptCall(
string $method,
object $arg,
ContextInterface $ctx,
callable $next
): object {
return (object)['method' => $method, 'arg' => $arg, 'ctx' => $ctx, 'next' => $next];
}
}]),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public function testFromPayload(): void
'state_transition_count' => 1,
'history_size_bytes' => 1,
'most_recent_worker_version_stamp' => (new \Temporal\Api\Common\V1\WorkerVersionStamp())
->setBundleId('bundleId')
->setBuildId('buildId')
->setUseVersioning(true),
]),
Expand Down Expand Up @@ -94,7 +93,6 @@ public function testFromPayload(): void
$this->assertTrue($info->autoResetPoints[0]->resettable);
$this->assertSame('binaryChecksum', $info->autoResetPoints[0]->binaryChecksum);
$this->assertSame(1, $info->historySizeBytes);
$this->assertSame('bundleId', $info->mostRecentWorkerVersionStamp->bundleId);
$this->assertSame('buildId', $info->mostRecentWorkerVersionStamp->buildId);
$this->assertTrue($info->mostRecentWorkerVersionStamp->useVersioning);
}
Expand Down
Loading