diff --git a/README.md b/README.md index 7390d47..0ad585d 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,15 @@ class AppKernel extends Kernel ## Configuration -Configuration is handled by the SDK rather than by the bundle, and no validation -is performed at compile time. Full documentation of the configuration options -available can be read in the [SDK Guide](http://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/configuration.html). +By default, configuration is handled by the SDK rather than by the bundle, and +no validation is performed at compile time. Full documentation of the +configuration options available can be read in the [SDK Guide](http://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/configuration.html). + +If AWS_MERGE_CONFIG environment variable is set to `true`, configuration +validation and merging are enabled. The bundle validates and merges known +configuration options, including for each service. Additional configuration +options can be included in a single configuration file, but merging will fail +if non-standard options are specified in more than once. To use a service for any configuration value, use `@` followed by the service name, such as `@a_service`. This syntax will be converted to a service during diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 4bd45d1..b784f24 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -10,12 +10,105 @@ class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { + // Maintain backwars compatibility, only merge when AWS_MERGE_CONFIG is set + $mergeConfig = getenv('AWS_MERGE_CONFIG') ?: false; + $treeType = 'variable'; + + if ($mergeConfig) { + $treeType = 'array'; + } + // Most recent versions of TreeBuilder have a constructor if (\method_exists(TreeBuilder::class, '__construct')) { - $treeBuilder = new TreeBuilder('aws', 'variable'); + $treeBuilder = new TreeBuilder('aws', $treeType); } else { // which is not the case for older versions $treeBuilder = new TreeBuilder; - $treeBuilder->root('aws', 'variable'); + $treeBuilder->root('aws', $treeType); + } + + // If not AWS_MERGE_CONFIG, return empty, variable TreeBuilder + if (!$mergeConfig) { + return $treeBuilder; + } + + $rootNode = $treeBuilder->root('aws'); + + // Define TreeBuilder to allow config validation and merging + $rootNode + ->ignoreExtraKeys(false) + ->children() + ->variableNode('credentials')->end() + ->variableNode('debug')->end() + ->variableNode('stats')->end() + ->scalarNode('endpoint')->end() + ->variableNode('endpoint_discovery')->end() + ->arrayNode('http') + ->children() + ->floatNode('connect_timeout')->end() + ->booleanNode('debug')->end() + ->booleanNode('decode_content')->end() + ->integerNode('delay')->end() + ->variableNode('expect')->end() + ->variableNode('proxy')->end() + ->scalarNode('sink')->end() + ->booleanNode('synchronous')->end() + ->booleanNode('stream')->end() + ->floatNode('timeout')->end() + ->scalarNode('verify')->end() + ->end() + ->end() + ->scalarNode('profile')->end() + ->scalarNode('region')->end() + ->integerNode('retries')->end() + ->scalarNode('scheme')->end() + ->scalarNode('service')->end() + ->scalarNode('signature_version')->end() + ->variableNode('ua_append')->end() + ->variableNode('validate')->end() + ->scalarNode('version')->end() + ->end() + ; + + //Setup config trees for each of the services + foreach (array_column(Aws\manifest(), 'namespace') as $awsService) { + $rootNode + ->children() + ->arrayNode($awsService) + ->ignoreExtraKeys(false) + ->children() + ->variableNode('credentials')->end() + ->variableNode('debug')->end() + ->variableNode('stats')->end() + ->scalarNode('endpoint')->end() + ->variableNode('endpoint_discovery')->end() + ->arrayNode('http') + ->children() + ->floatNode('connect_timeout')->end() + ->booleanNode('debug')->end() + ->booleanNode('decode_content')->end() + ->integerNode('delay')->end() + ->variableNode('expect')->end() + ->variableNode('proxy')->end() + ->scalarNode('sink')->end() + ->booleanNode('synchronous')->end() + ->booleanNode('stream')->end() + ->floatNode('timeout')->end() + ->scalarNode('verify')->end() + ->end() + ->end() + ->scalarNode('profile')->end() + ->scalarNode('region')->end() + ->integerNode('retries')->end() + ->scalarNode('scheme')->end() + ->scalarNode('service')->end() + ->scalarNode('signature_version')->end() + ->variableNode('ua_append')->end() + ->variableNode('validate')->end() + ->scalarNode('version')->end() + ->end() + ->end() + ->end() + ; } return $treeBuilder; diff --git a/tests/DependencyInjection/AwsExtensionTest.php b/tests/DependencyInjection/AwsExtensionTest.php index f1e8b48..48c6501 100644 --- a/tests/DependencyInjection/AwsExtensionTest.php +++ b/tests/DependencyInjection/AwsExtensionTest.php @@ -121,4 +121,120 @@ public function extension_should_expand_service_references() $extension->load([$config], $container); } + + /** + * @test + */ + public function extension_should_validate_and_merge_configs() + { + putenv('AWS_MERGE_CONFIG=true'); + $extension = new AwsExtension; + $config = [ + 'credentials' => false, + 'debug' => [ + 'http' => true + ], + 'stats' => [ + 'http' => true + ], + 'retries' => 5, + 'endpoint' => 'http://localhost:8000', + 'endpoint_discovery' => [ + 'enabled' => true, + 'cache_limit' => 1000 + ], + 'http' => [ + 'connect_timeout' => 5.5, + 'debug' => true, + 'decode_content' => true, + 'delay' => 1, + 'expect' => true, + 'proxy' => 'http://localhost:9000', + 'sink' => '/path/to/sink', + 'synchronous' => true, + 'stream' => true, + 'timeout' => 3.14, + 'verify' => '/path/to/ca_cert_bundle' + ], + 'profile' => 'prod', + 'region' => 'us-west-2', + 'retries' => 5, + 'scheme' => 'http', + 'signature_version' => 'v4', + 'ua_append' => [ + 'prod', + 'foo' + ], + 'validate' => [ + 'required' => true + ], + 'version' => 'latest', + 'S3' => [ + 'version' => '2006-03-01', + ] + ]; + $configDev = [ + 'credentials' => '@aws_sdk', + 'debug' => true, + 'stats' => true, + 'ua_append' => 'dev', + 'validate' => true, + ]; + $container = $this->getMockBuilder(ContainerBuilder::class) + ->setMethods(['getDefinition', 'replaceArgument']) + ->getMock(); + $container->expects($this->once()) + ->method('getDefinition') + ->with('aws_sdk') + ->willReturnSelf(); + $container->expects($this->once()) + ->method('replaceArgument') + ->with(0, $this->callback(function ($arg) { + return is_array($arg) + && isset($arg['credentials']) + && $arg['credentials'] instanceof Reference + && (string) $arg['credentials'] === 'aws_sdk' + && isset($arg['debug']) + && (bool) $arg['debug'] === true + && isset($arg['stats']) + && (bool) $arg['stats'] === true + && isset($arg['retries']) + && (integer) $arg['retries'] === 5 + && isset($arg['endpoint']) + && (string) $arg['endpoint'] === 'http://localhost:8000' + && isset($arg['validate']) + && (bool) $arg['validate'] === true + && isset($arg['endpoint_discovery']['enabled']) + && isset($arg['endpoint_discovery']['cache_limit']) + && (bool) $arg['endpoint_discovery']['enabled'] === true + && (integer) $arg['endpoint_discovery']['cache_limit'] === 1000 + && isset($arg['S3']['version']) + && (string) $arg['S3']['version'] === '2006-03-01' + ; + })); + + $extension->load([$config, $configDev], $container); + } + + /** + * @test + * + * @expectedException RuntimeException + */ + public function extension_should_error_merging_unknown_config_options() + { + putenv('AWS_MERGE_CONFIG=true'); + $extension = new AwsExtension; + $config = [ + 'foo' => 'bar' + ]; + $configDev = [ + 'foo' => 'baz' + ]; + $container = $this->getMockBuilder(ContainerBuilder::class) + ->setMethods(['getDefinition', 'replaceArgument']) + ->getMock(); + + $extension->load([$config, $configDev], $container); + } }