ConfigurationProvider.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <?php
  2. namespace Aws\EndpointDiscovery;
  3. use Aws\AbstractConfigurationProvider;
  4. use Aws\CacheInterface;
  5. use Aws\ConfigurationProviderInterface;
  6. use Aws\EndpointDiscovery\Exception\ConfigurationException;
  7. use GuzzleHttp\Promise;
  8. use GuzzleHttp\Promise\PromiseInterface;
  9. /**
  10. * A configuration provider is a function that returns a promise that is
  11. * fulfilled with a {@see \Aws\EndpointDiscovery\ConfigurationInterface}
  12. * or rejected with an {@see \Aws\EndpointDiscovery\Exception\ConfigurationException}.
  13. *
  14. * <code>
  15. * use Aws\EndpointDiscovery\ConfigurationProvider;
  16. * $provider = ConfigurationProvider::defaultProvider();
  17. * // Returns a ConfigurationInterface or throws.
  18. * $config = $provider()->wait();
  19. * </code>
  20. *
  21. * Configuration providers can be composed to create configuration using
  22. * conditional logic that can create different configurations in different
  23. * environments. You can compose multiple providers into a single provider using
  24. * {@see Aws\EndpointDiscovery\ConfigurationProvider::chain}. This function
  25. * accepts providers as variadic arguments and returns a new function that will
  26. * invoke each provider until a successful configuration is returned.
  27. *
  28. * <code>
  29. * // First try an INI file at this location.
  30. * $a = ConfigurationProvider::ini(null, '/path/to/file.ini');
  31. * // Then try an INI file at this location.
  32. * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini');
  33. * // Then try loading from environment variables.
  34. * $c = ConfigurationProvider::env();
  35. * // Combine the three providers together.
  36. * $composed = ConfigurationProvider::chain($a, $b, $c);
  37. * // Returns a promise that is fulfilled with a configuration or throws.
  38. * $promise = $composed();
  39. * // Wait on the configuration to resolve.
  40. * $config = $promise->wait();
  41. * </code>
  42. */
  43. class ConfigurationProvider extends AbstractConfigurationProvider
  44. implements ConfigurationProviderInterface
  45. {
  46. const DEFAULT_ENABLED = false;
  47. const DEFAULT_CACHE_LIMIT = 1000;
  48. const ENV_ENABLED = 'AWS_ENDPOINT_DISCOVERY_ENABLED';
  49. const ENV_ENABLED_ALT = 'AWS_ENABLE_ENDPOINT_DISCOVERY';
  50. const ENV_PROFILE = 'AWS_PROFILE';
  51. public static $cacheKey = 'aws_cached_endpoint_discovery_config';
  52. protected static $interfaceClass = ConfigurationInterface::class;
  53. protected static $exceptionClass = ConfigurationException::class;
  54. /**
  55. * Create a default config provider that first checks for environment
  56. * variables, then checks for a specified profile in the environment-defined
  57. * config file location (env variable is 'AWS_CONFIG_FILE', file location
  58. * defaults to ~/.aws/config), then checks for the "default" profile in the
  59. * environment-defined config file location, and failing those uses a default
  60. * fallback set of configuration options.
  61. *
  62. * This provider is automatically wrapped in a memoize function that caches
  63. * previously provided config options.
  64. *
  65. * @param array $config
  66. *
  67. * @return callable
  68. */
  69. public static function defaultProvider(array $config = [])
  70. {
  71. $configProviders = [self::env()];
  72. if (
  73. !isset($config['use_aws_shared_config_files'])
  74. || $config['use_aws_shared_config_files'] != false
  75. ) {
  76. $configProviders[] = self::ini();
  77. }
  78. $configProviders[] = self::fallback($config);
  79. $memo = self::memoize(
  80. call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)
  81. );
  82. if (isset($config['endpoint_discovery'])
  83. && $config['endpoint_discovery'] instanceof CacheInterface
  84. ) {
  85. return self::cache($memo, $config['endpoint_discovery'], self::$cacheKey);
  86. }
  87. return $memo;
  88. }
  89. /**
  90. * Provider that creates config from environment variables.
  91. *
  92. * @param $cacheLimit
  93. * @return callable
  94. */
  95. public static function env($cacheLimit = self::DEFAULT_CACHE_LIMIT)
  96. {
  97. return function () use ($cacheLimit) {
  98. // Use config from environment variables, if available
  99. $enabled = getenv(self::ENV_ENABLED);
  100. if ($enabled === false || $enabled === '') {
  101. $enabled = getenv(self::ENV_ENABLED_ALT);
  102. }
  103. if ($enabled !== false && $enabled !== '') {
  104. return Promise\Create::promiseFor(
  105. new Configuration($enabled, $cacheLimit)
  106. );
  107. }
  108. return self::reject('Could not find environment variable config'
  109. . ' in ' . self::ENV_ENABLED);
  110. };
  111. }
  112. /**
  113. * Fallback config options when other sources are not set. Will check the
  114. * service model for any endpoint discovery required operations, and enable
  115. * endpoint discovery in that case. If no required operations found, will use
  116. * the class default values.
  117. *
  118. * @param array $config
  119. * @return callable
  120. */
  121. public static function fallback($config = [])
  122. {
  123. $enabled = self::DEFAULT_ENABLED;
  124. if (!empty($config['api_provider'])
  125. && !empty($config['service'])
  126. && !empty($config['version'])
  127. ) {
  128. $provider = $config['api_provider'];
  129. $apiData = $provider('api', $config['service'], $config['version']);
  130. if (!empty($apiData['operations'])) {
  131. foreach ($apiData['operations'] as $operation) {
  132. if (!empty($operation['endpointdiscovery']['required'])) {
  133. $enabled = true;
  134. }
  135. }
  136. }
  137. }
  138. return function () use ($enabled) {
  139. return Promise\Create::promiseFor(
  140. new Configuration(
  141. $enabled,
  142. self::DEFAULT_CACHE_LIMIT
  143. )
  144. );
  145. };
  146. }
  147. /**
  148. * Config provider that creates config using a config file whose location
  149. * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to
  150. * ~/.aws/config if not specified
  151. *
  152. * @param string|null $profile Profile to use. If not specified will use
  153. * the "default" profile.
  154. * @param string|null $filename If provided, uses a custom filename rather
  155. * than looking in the default directory.
  156. * @param int $cacheLimit
  157. *
  158. * @return callable
  159. */
  160. public static function ini(
  161. $profile = null,
  162. $filename = null,
  163. $cacheLimit = self::DEFAULT_CACHE_LIMIT
  164. ) {
  165. $filename = $filename ?: (self::getDefaultConfigFilename());
  166. $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');
  167. return function () use ($profile, $filename, $cacheLimit) {
  168. if (!@is_readable($filename)) {
  169. return self::reject("Cannot read configuration from $filename");
  170. }
  171. $data = \Aws\parse_ini_file($filename, true);
  172. if ($data === false) {
  173. return self::reject("Invalid config file: $filename");
  174. }
  175. if (!isset($data[$profile])) {
  176. return self::reject("'$profile' not found in config file");
  177. }
  178. if (!isset($data[$profile]['endpoint_discovery_enabled'])) {
  179. return self::reject("Required endpoint discovery config values
  180. not present in INI profile '{$profile}' ({$filename})");
  181. }
  182. return Promise\Create::promiseFor(
  183. new Configuration(
  184. $data[$profile]['endpoint_discovery_enabled'],
  185. $cacheLimit
  186. )
  187. );
  188. };
  189. }
  190. /**
  191. * Unwraps a configuration object in whatever valid form it is in,
  192. * always returning a ConfigurationInterface object.
  193. *
  194. * @param mixed $config
  195. * @return ConfigurationInterface
  196. * @throws \InvalidArgumentException
  197. */
  198. public static function unwrap($config)
  199. {
  200. if (is_callable($config)) {
  201. $config = $config();
  202. }
  203. if ($config instanceof PromiseInterface) {
  204. $config = $config->wait();
  205. }
  206. if ($config instanceof ConfigurationInterface) {
  207. return $config;
  208. } elseif (is_array($config) && isset($config['enabled'])) {
  209. if (isset($config['cache_limit'])) {
  210. return new Configuration(
  211. $config['enabled'],
  212. $config['cache_limit']
  213. );
  214. }
  215. return new Configuration(
  216. $config['enabled'],
  217. self::DEFAULT_CACHE_LIMIT
  218. );
  219. }
  220. throw new \InvalidArgumentException('Not a valid endpoint_discovery '
  221. . 'configuration argument.');
  222. }
  223. }