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

[azure-api-client] Use named arguments instead of optons array #68

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
50 changes: 31 additions & 19 deletions libs/azure-api-client/README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,51 @@
# Azure API Client

## Installation

```bash
composer require keboola/azure-api-client
```

## Usage

To create API client using PHP:
The simplest way to use API client is just by creating its instance.

```php
# example.php
use Keboola\AzureApiClient\Authentication\AuthenticatorFactory;
use Keboola\AzureApiClient\ApiClientFactory\PlainAzureApiClientFactory;
use Keboola\AzureApiClient\GuzzleClientFactory;
use Keboola\AzureApiClient\Marketplace\MarketplaceApiClient;
use Monolog\Logger;

$authenticatorFactory = new AuthenticatorFactory();
$clientFactory = new PlainAzureApiClientFactory($guzzleClientFactory, $authenticatorFactory, $logger);

$marketingApiClient = MarketplaceApiClient::create($clientFactory);
$logger = new Logger('azure-api-client');
$marketplaces = new MarketplaceApiClient([
'logger' => $logger,
]);
```

To create API client using Symfony container, just register class services and everything should auto-wire just fine:
```yaml
# services.yaml
services:
Keboola\AzureApiClient\Authentication\AuthenticatorFactory:
### Authentication
By default, API client will try to authenticate using ENV variables `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and
`AZURE_CLIENT_SECRET`. If some of them is not available, it'll fall back to Azure metadata API.

If you want to supply your own credentials, you can pass custom authenticator instance in the client options:
```php
use Keboola\AzureApiClient\Authentication\Authenticator\ClientCredentialsAuthenticator;
use Keboola\AzureApiClient\Marketplace\MarketplaceApiClient;

$logger = new Logger('azure-api-client');
$marketplaces = new MarketplaceApiClient([
'authenticator' => new ClientCredentialsAuthenticator(
'tenant-id',
'client-id',
'client-secret',
),
]);
```

Keboola\AzureApiClient\AzureApiClientFactory:
Or can provide custom authentication token directly:
```php
use Keboola\AzureApiClient\Authentication\Authenticator\StaticTokenAuthenticator;
use Keboola\AzureApiClient\Marketplace\MarketplaceApiClient;

Keboola\AzureApiClient\Marketplace\MarketplaceApiClient:
factory: ['App\Marketplace\Azure\MarketplaceApiClient\Marketplace\MarketplaceApiClient', 'create']
$logger = new Logger('azure-api-client');
$marketplaces = new MarketplaceApiClient([
'authenticator' => new StaticTokenAuthenticator('my-token'),
]);
```

## License
Expand Down
3 changes: 2 additions & 1 deletion libs/azure-api-client/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"php": "^8.1",
"guzzlehttp/guzzle": "^7.5",
"monolog/monolog": "^2.0|^3.0",
"symfony/validator": "^5.0|^6.0"
"webmozart/assert": "^1.11"
},
"config": {
"lock": false,
Expand All @@ -38,6 +38,7 @@
"keboola/coding-standard": "^14.0",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-phpunit": "^1.3",
"phpstan/phpstan-webmozart-assert": "^1.2",
"sempro/phpunit-pretty-print": "^1.4",
"symfony/http-client": "^6.2"
},
Expand Down
1 change: 1 addition & 0 deletions libs/azure-api-client/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ parameters:

includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-webmozart-assert/extension.neon
94 changes: 37 additions & 57 deletions libs/azure-api-client/src/ApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use JsonException;
use Keboola\AzureApiClient\Authentication\Authenticator\AuthenticatorInterface;
use Keboola\AzureApiClient\Authentication\Authenticator\SystemAuthenticatorResolver;
use Keboola\AzureApiClient\Authentication\AuthorizationHeaderResolver;
use Keboola\AzureApiClient\Exception\ClientException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Validation;
use Throwable;
use Webmozart\Assert\Assert;

class ApiClient
{
Expand All @@ -28,76 +29,44 @@ class ApiClient

private readonly HandlerStack $requestHandlerStack;
private readonly GuzzleClient $httpClient;
private readonly LoggerInterface $logger;
private readonly AuthenticatorInterface $authenticator;

/**
* @param array{
* baseUrl?: null|non-empty-string,
* backoffMaxTries?: null|int<0, max>,
* middleware?: null|list<callable>,
* requestHandler?: null|callable,
* logger?: null|LoggerInterface,
* } $options
* @param non-empty-string|null $baseUrl
* @param int<0, max>|null $backoffMaxTries
*/
public function __construct(
array $options = [],
?string $baseUrl = null,
?int $backoffMaxTries = null,
?AuthenticatorInterface $authenticator = null,
?callable $requestHandler = null,
?LoggerInterface $logger = null,
) {
$errors = Validation::createValidator()->validate($options, new Assert\Collection([
'allowMissingFields' => true,
'allowExtraFields' => false,
'fields' => [
'baseUrl' => new Assert\Sequentially([
new Assert\Url(),
new Assert\Length(['min' => 1]),
]),

'backoffMaxTries' => new Assert\Sequentially([
new Assert\Type('int'),
new Assert\GreaterThanOrEqual(0),
]),

'middleware' => new Assert\All([
new Assert\Type('callable'),
]),

'requestHandler' => new Assert\Type('callable'),

'logger' => new Assert\Type(LoggerInterface::class),
],
]));

if ($errors->count() > 0) {
$messages = array_map(
fn(ConstraintViolationInterface $error) => sprintf(
'%s: %s',
$error->getPropertyPath(),
$error->getMessage()
),
iterator_to_array($errors),
);

throw new ClientException(sprintf('Invalid options when creating client: %s', implode("\n", $messages)));
}
$backoffMaxTries ??= self::DEFAULT_BACKOFF_RETRIES;
$logger ??= new NullLogger();

$this->logger = $options['logger'] ?? new NullLogger();
Assert::nullOrMinLength($baseUrl, 1);
Assert::greaterThanEq($backoffMaxTries, 0);

$this->requestHandlerStack = HandlerStack::create($options['requestHandler'] ?? null);
foreach ($options['middleware'] ?? [] as $middleware) {
$this->requestHandlerStack->push($middleware);
}
$this->authenticator = $authenticator ?? new SystemAuthenticatorResolver(
backoffMaxTries: $backoffMaxTries,
requestHandler: $requestHandler ? $requestHandler(...) : null,
logger: $logger,
);

$this->requestHandlerStack = HandlerStack::create($requestHandler);

$backoffMaxTries = $options['backoffMaxTries'] ?? self::DEFAULT_BACKOFF_RETRIES;
if ($backoffMaxTries > 0) {
$this->requestHandlerStack->push(Middleware::retry(new RetryDecider($backoffMaxTries, $this->logger)));
$this->requestHandlerStack->push(Middleware::retry(new RetryDecider($backoffMaxTries, $logger)));
}

$this->requestHandlerStack->push(Middleware::log($this->logger, new MessageFormatter(
$this->requestHandlerStack->push(Middleware::log($logger, new MessageFormatter(
'{hostname} {req_header_User-Agent} - [{ts}] "{method} {resource} {protocol}/{version}"' .
' {code} {res_header_Content-Length}'
)));

$this->httpClient = new GuzzleClient([
'base_uri' => $options['baseUrl'] ?? null,
'base_uri' => $baseUrl,
'handler' => $this->requestHandlerStack,
'headers' => [
'User-Agent' => self::USER_AGENT,
Expand All @@ -107,6 +76,17 @@ public function __construct(
]);
}

public function authenticate(string $resource): void
{
$middleware = Middleware::mapRequest(new AuthorizationHeaderResolver(
$this->authenticator,
$resource
));

$this->requestHandlerStack->remove('auth');
$this->requestHandlerStack->push($middleware, 'auth');
}

/**
* @template TResponseClass of ResponseModelInterface
* @param class-string<TResponseClass> $responseClass
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

16 changes: 16 additions & 0 deletions libs/azure-api-client/src/Authentication/AuthenticationToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Keboola\AzureApiClient\Authentication;

use DateTimeImmutable;

class AuthenticationToken
{
public function __construct(
public readonly string $value,
public readonly ?DateTimeImmutable $expiresAt,
) {
}
}
Loading