241 lines
8.7 KiB
PHP
241 lines
8.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace AsyncAws\Core;
|
|
|
|
use AsyncAws\Core\Credentials\IniFileLoader;
|
|
use AsyncAws\Core\Exception\InvalidArgument;
|
|
|
|
/**
|
|
* Helper object that holds all configuration to the API.
|
|
*
|
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
|
*/
|
|
final class Configuration
|
|
{
|
|
public const DEFAULT_REGION = 'us-east-1';
|
|
|
|
public const OPTION_REGION = 'region';
|
|
public const OPTION_DEBUG = 'debug';
|
|
public const OPTION_PROFILE = 'profile';
|
|
public const OPTION_ACCESS_KEY_ID = 'accessKeyId';
|
|
public const OPTION_SECRET_ACCESS_KEY = 'accessKeySecret';
|
|
public const OPTION_SESSION_TOKEN = 'sessionToken';
|
|
public const OPTION_SHARED_CREDENTIALS_FILE = 'sharedCredentialsFile';
|
|
public const OPTION_SHARED_CONFIG_FILE = 'sharedConfigFile';
|
|
public const OPTION_ENDPOINT = 'endpoint';
|
|
public const OPTION_ROLE_ARN = 'roleArn';
|
|
public const OPTION_WEB_IDENTITY_TOKEN_FILE = 'webIdentityTokenFile';
|
|
public const OPTION_ROLE_SESSION_NAME = 'roleSessionName';
|
|
public const OPTION_CONTAINER_CREDENTIALS_RELATIVE_URI = 'containerCredentialsRelativeUri';
|
|
public const OPTION_ENDPOINT_DISCOVERY_ENABLED = 'endpointDiscoveryEnabled';
|
|
|
|
// S3 specific option
|
|
public const OPTION_PATH_STYLE_ENDPOINT = 'pathStyleEndpoint';
|
|
public const OPTION_SEND_CHUNKED_BODY = 'sendChunkedBody';
|
|
|
|
private const AVAILABLE_OPTIONS = [
|
|
self::OPTION_REGION => true,
|
|
self::OPTION_DEBUG => true,
|
|
self::OPTION_PROFILE => true,
|
|
self::OPTION_ACCESS_KEY_ID => true,
|
|
self::OPTION_SECRET_ACCESS_KEY => true,
|
|
self::OPTION_SESSION_TOKEN => true,
|
|
self::OPTION_SHARED_CREDENTIALS_FILE => true,
|
|
self::OPTION_SHARED_CONFIG_FILE => true,
|
|
self::OPTION_ENDPOINT => true,
|
|
self::OPTION_ROLE_ARN => true,
|
|
self::OPTION_WEB_IDENTITY_TOKEN_FILE => true,
|
|
self::OPTION_ROLE_SESSION_NAME => true,
|
|
self::OPTION_CONTAINER_CREDENTIALS_RELATIVE_URI => true,
|
|
self::OPTION_ENDPOINT_DISCOVERY_ENABLED => true,
|
|
self::OPTION_PATH_STYLE_ENDPOINT => true,
|
|
self::OPTION_SEND_CHUNKED_BODY => true,
|
|
];
|
|
|
|
// Put fallback options into groups to avoid mixing of provided config and environment variables
|
|
private const FALLBACK_OPTIONS = [
|
|
[self::OPTION_REGION => ['AWS_REGION', 'AWS_DEFAULT_REGION']],
|
|
[self::OPTION_PROFILE => ['AWS_PROFILE', 'AWS_DEFAULT_PROFILE']],
|
|
[
|
|
self::OPTION_ACCESS_KEY_ID => ['AWS_ACCESS_KEY_ID', 'AWS_ACCESS_KEY'],
|
|
self::OPTION_SECRET_ACCESS_KEY => ['AWS_SECRET_ACCESS_KEY', 'AWS_SECRET_KEY'],
|
|
self::OPTION_SESSION_TOKEN => 'AWS_SESSION_TOKEN',
|
|
],
|
|
[self::OPTION_SHARED_CREDENTIALS_FILE => 'AWS_SHARED_CREDENTIALS_FILE'],
|
|
[self::OPTION_SHARED_CONFIG_FILE => 'AWS_CONFIG_FILE'],
|
|
[
|
|
self::OPTION_ROLE_ARN => 'AWS_ROLE_ARN',
|
|
self::OPTION_WEB_IDENTITY_TOKEN_FILE => 'AWS_WEB_IDENTITY_TOKEN_FILE',
|
|
self::OPTION_ROLE_SESSION_NAME => 'AWS_ROLE_SESSION_NAME',
|
|
],
|
|
[self::OPTION_CONTAINER_CREDENTIALS_RELATIVE_URI => 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'],
|
|
[self::OPTION_ENDPOINT_DISCOVERY_ENABLED => ['AWS_ENDPOINT_DISCOVERY_ENABLED', 'AWS_ENABLE_ENDPOINT_DISCOVERY']],
|
|
];
|
|
|
|
private const DEFAULT_OPTIONS = [
|
|
self::OPTION_REGION => self::DEFAULT_REGION,
|
|
self::OPTION_DEBUG => 'false',
|
|
self::OPTION_PROFILE => 'default',
|
|
self::OPTION_SHARED_CREDENTIALS_FILE => '~/.aws/credentials',
|
|
self::OPTION_SHARED_CONFIG_FILE => '~/.aws/config',
|
|
// https://docs.aws.amazon.com/general/latest/gr/rande.html
|
|
self::OPTION_ENDPOINT => 'https://%service%.%region%.amazonaws.com',
|
|
self::OPTION_PATH_STYLE_ENDPOINT => 'false',
|
|
self::OPTION_SEND_CHUNKED_BODY => 'false',
|
|
self::OPTION_ENDPOINT_DISCOVERY_ENABLED => 'false',
|
|
];
|
|
|
|
private $data = [];
|
|
|
|
private $userData = [];
|
|
|
|
public static function create(array $options)
|
|
{
|
|
if (0 < \count($invalidOptions = array_diff_key($options, self::AVAILABLE_OPTIONS))) {
|
|
throw new InvalidArgument(sprintf('Invalid option(s) "%s" passed to "%s::%s". ', implode('", "', array_keys($invalidOptions)), __CLASS__, __METHOD__));
|
|
}
|
|
|
|
// Force each option to be string or null
|
|
$options = array_map(static function ($value) {
|
|
return null !== $value ? (string) $value : $value;
|
|
}, $options);
|
|
|
|
$configuration = new self();
|
|
$options = self::parseEnvironmentVariables($options);
|
|
self::populateConfiguration($configuration, $options);
|
|
$iniOptions = self::parseIniFiles($configuration);
|
|
self::populateConfiguration($configuration, $iniOptions);
|
|
|
|
return $configuration;
|
|
}
|
|
|
|
public static function optionExists(string $optionName): bool
|
|
{
|
|
return isset(self::AVAILABLE_OPTIONS[$optionName]);
|
|
}
|
|
|
|
/**
|
|
* @psalm-return (
|
|
* $name is
|
|
* self::OPTION_REGION
|
|
* |self::OPTION_DEBUG
|
|
* |self::OPTION_PROFILE
|
|
* |self::OPTION_SHARED_CREDENTIALS_FILE
|
|
* |self::OPTION_SHARED_CONFIG_FILE
|
|
* |self::OPTION_ENDPOINT
|
|
* |self::OPTION_PATH_STYLE_ENDPOINT
|
|
* |self::OPTION_SEND_CHUNKED_BODY
|
|
* ? string
|
|
* : ?string
|
|
* )
|
|
*/
|
|
public function get(string $name): ?string
|
|
{
|
|
if (!isset(self::AVAILABLE_OPTIONS[$name])) {
|
|
throw new InvalidArgument(sprintf('Invalid option "%s" passed to "%s::%s". ', $name, __CLASS__, __METHOD__));
|
|
}
|
|
|
|
return $this->data[$name] ?? null;
|
|
}
|
|
|
|
public function has(string $name): bool
|
|
{
|
|
if (!isset(self::AVAILABLE_OPTIONS[$name])) {
|
|
throw new InvalidArgument(sprintf('Invalid option "%s" passed to "%s::%s". ', $name, __CLASS__, __METHOD__));
|
|
}
|
|
|
|
return isset($this->data[$name]);
|
|
}
|
|
|
|
public function isDefault(string $name): bool
|
|
{
|
|
if (!isset(self::AVAILABLE_OPTIONS[$name])) {
|
|
throw new InvalidArgument(sprintf('Invalid option "%s" passed to "%s::%s". ', $name, __CLASS__, __METHOD__));
|
|
}
|
|
|
|
return empty($this->userData[$name]);
|
|
}
|
|
|
|
private static function parseEnvironmentVariables(array $options): array
|
|
{
|
|
foreach (self::FALLBACK_OPTIONS as $fallbackGroup) {
|
|
// prevent mixing env variables with config keys
|
|
foreach ($fallbackGroup as $option => $envVariableNames) {
|
|
if (isset($options[$option])) {
|
|
continue 2;
|
|
}
|
|
}
|
|
|
|
foreach ($fallbackGroup as $option => $envVariableNames) {
|
|
// Read environment files
|
|
$envVariableNames = (array) $envVariableNames;
|
|
foreach ($envVariableNames as $envVariableName) {
|
|
if (null !== $envVariableValue = EnvVar::get($envVariableName)) {
|
|
$options[$option] = $envVariableValue;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Look for "region" in the configured ini files.
|
|
*/
|
|
private static function parseIniFiles(Configuration $configuration): array
|
|
{
|
|
$options = [];
|
|
if (!$configuration->isDefault(self::OPTION_REGION)) {
|
|
return $options;
|
|
}
|
|
|
|
$profilesData = (new IniFileLoader())->loadProfiles([
|
|
$configuration->get(self::OPTION_SHARED_CREDENTIALS_FILE),
|
|
$configuration->get(self::OPTION_SHARED_CONFIG_FILE),
|
|
]);
|
|
|
|
if (empty($profilesData)) {
|
|
return $options;
|
|
}
|
|
|
|
/** @var string $profile */
|
|
$profile = $configuration->get(Configuration::OPTION_PROFILE);
|
|
if (isset($profilesData[$profile]['region'])) {
|
|
$options[self::OPTION_REGION] = $profilesData[$profile]['region'];
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Add array options to the configuration object.
|
|
*/
|
|
private static function populateConfiguration(Configuration $configuration, array $options): void
|
|
{
|
|
foreach ($options as $key => $value) {
|
|
if (null !== $value) {
|
|
$configuration->userData[$key] = true;
|
|
}
|
|
}
|
|
|
|
// If we have not applied default before
|
|
if (empty($configuration->data)) {
|
|
foreach (self::DEFAULT_OPTIONS as $optionTrigger => $defaultValue) {
|
|
if (isset($options[$optionTrigger])) {
|
|
continue;
|
|
}
|
|
|
|
$options[$optionTrigger] = $defaultValue;
|
|
}
|
|
}
|
|
|
|
$configuration->data = array_merge($configuration->data, $options);
|
|
}
|
|
}
|