diff --git a/src/ClientResolver.php b/src/ClientResolver.php index 7a8dc0cfac..b33d5c0503 100644 --- a/src/ClientResolver.php +++ b/src/ClientResolver.php @@ -21,6 +21,9 @@ */ class ClientResolver { + const ENV_FORMAT_REGION_SERVICE = 'AWS_%s_%s_ENDPOINT'; + const ENV_FORMAT_SERVICE = 'AWS_%s_ENDPOINT'; + /** @var array */ private $argDefinitions; @@ -435,6 +438,25 @@ public static function _apply_api_provider(callable $value, array &$args) public static function _apply_endpoint_provider(callable $value, array &$args) { if (!isset($args['endpoint'])) { + $regionEnvName = build_env_name(self::ENV_FORMAT_REGION_SERVICE, $args['region'], $args['service']); + $serviceEnvName = build_env_name(self::ENV_FORMAT_SERVICE, $args['service']); + + /** + * Search for an environment variable endpoint configuration with this priority: + * - AWS___ENDPOINT + * - AWS__ENDPOINT + * + * Future development consideration should be made with getenv's $local_only optional to ensure + * environment variables are configurable using putenv in FastCGI environments + * @see http://php.net/manual/function.getenv.php#refsect1-function.getenv-notes getenv FastCGI Note + */ + $envValue = getenv($regionEnvName) + ?: getenv($serviceEnvName); + if ($envValue) { + $args['endpoint'] = $envValue; + return; + } + $endpointPrefix = isset($args['api']['metadata']['endpointPrefix']) ? $args['api']['metadata']['endpointPrefix'] : $args['service']; diff --git a/src/functions.php b/src/functions.php index 1b0a57a16e..fa91bde67b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -349,3 +349,33 @@ function manifest($service = null) ); } } + + +/** + * Build the environment variable name from a formatted string using UPPER_CAMEL_CASE + * Note: parameters will be prepared using the prepare_env_name() function + * @see http://php.net/manual/function.sprintf.php + * + * @param string $format The formatted string used as the template + * @param string ...$values Values to be used + * @return string Returns the formatted environment variable name + */ +function build_env_name() +{ + $values = func_get_args(); + $format = array_shift($values); + + $prepared = array_map('\Aws\prepare_env_name', $values); + return call_user_func_array('sprintf', array_merge([$format], $prepared)); +} + +/** + * Converts a string to all caps and replaces hyphens (-) with underscores (_) to produce UPPER_CAMEL_CASE + * + * @param string $source + * @return string + */ +function prepare_env_name($source) +{ + return str_replace('-', '_', strtoupper($source)); +} diff --git a/tests/ClientResolverTest.php b/tests/ClientResolverTest.php index 097c2df64b..f1f7f470f9 100644 --- a/tests/ClientResolverTest.php +++ b/tests/ClientResolverTest.php @@ -2,12 +2,14 @@ namespace Aws\Test; use Aws\Api\Service; +use function Aws\build_env_name; use Aws\ClientResolver; use Aws\CommandInterface; use Aws\Credentials\CredentialProvider; use Aws\Credentials\Credentials; use Aws\DynamoDb\DynamoDbClient; use Aws\Endpoint\Partition; +use Aws\Endpoint\PartitionInterface; use Aws\LruArrayCache; use Aws\S3\S3Client; use Aws\HandlerList; @@ -644,6 +646,56 @@ public function endpointProviderReturnProvider() ]; } + /** + * @dataProvider endpointValueConfiguredInEnvironmentVariableProvider + * + * @param string $service + * @param string $region + * @param string $envName + * @param string $envValue + */ + public function testResolvesEndpointValueConfiguredInEnvironmentVariable( + $service, + $region, + $envName, + $envValue + ) { + // reset the environment variables + putenv(build_env_name(ClientResolver::ENV_FORMAT_REGION_SERVICE, $region, $service) . '='); + putenv(build_env_name(ClientResolver::ENV_FORMAT_SERVICE, $service) . '='); + // apply given environment + putenv($envName . '=' . $envValue); + + $testValues = [ + 'endpoint_provider' => $this->createMock('\Aws\Endpoint\PartitionInterface'), + 'service' => $service, + 'region' => $region, + 'scheme' => 'http' + ]; + $testValues['endpoint_provider']->method('isRegionMatch')->willReturn(true); + $testValues['endpoint_provider']->method('__invoke')->willReturn([ + 'endpoint' => 'this-would-be-the-actual-endpoint' + ]); + + $resolverArgs = array_intersect_key( + ClientResolver::getDefaultArguments(), + array_flip(array_keys($testValues)) + ); + $resolver = new ClientResolver($resolverArgs); + + $resolved = $resolver->resolve($testValues, new HandlerList); + $this->assertSame($envValue, $resolved['endpoint']); + } + + public function endpointValueConfiguredInEnvironmentVariableProvider() + { + return [ + // Using region and service specific environment variable + ['service' => 's3', 'region' => 'us-west-1', 'envName' => 'AWS_US_WEST_1_S3_ENDPOINT', 'envValue' => 'foo'], + // Using service specific environment variable only + ['service' => 'my-service', 'region' => 'south-pole-1', 'envName' => 'AWS_MY_SERVICE_ENDPOINT', 'envValue' => 'bar'] + ]; + } /** * @dataProvider partitionReturnProvider diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index d05a408424..19abeb8963 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -130,4 +130,15 @@ public function testSerializesHttpRequests() $this->assertTrue($request->hasHeader('X-Amz-Date')); $this->assertEquals('123', (string) $request->getBody()); } + + /** + * @covers \Aws\build_env_name + * @covers \Aws\prepare_env_name + */ + public function testBuildEnvName() + { + $this->assertSame('FOO_BAR_BAZ', Aws\build_env_name('FOO_%s_%s', 'bar', 'bAz')); + $this->assertSame('FOO_BAR_BAZ', Aws\build_env_name('FOO_%s', 'Bar-bAZ')); + $this->assertSame('THIS_DOG_SWAM_TO_MY_FISH', Aws\build_env_name('%s_SWAM_TO_%s', 'this-dog', 'my-fish')); + } }