Skip to content

Commit 0d22d7e

Browse files
authored
Merge pull request #589 from Jean-Beru/add-cloudfront-support
Add CloudFront support
2 parents ef1bab7 + 4b1c865 commit 0d22d7e

File tree

12 files changed

+283
-2
lines changed

12 files changed

+283
-2
lines changed

.github/workflows/php.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
- php-version: '7.4'
2222
- php-version: '8.0'
2323
- php-version: '8.1'
24+
dependencies: 'jean-beru/fos-http-cache-cloudfront'
2425
- php-version: '7.4'
2526
symfony-version: '4.*'
2627
- php-version: '7.4'

Resources/doc/reference/configuration/proxy-client.rst

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,55 @@ endpoint for testing purposes.
276276

277277
.. _configuration_noop_proxy_client:
278278

279+
cloudfront
280+
----------
281+
Talking to AWS cloudfront requires the ``jean-beru/fos-http-cache-cloudfront`` library. You need to require this dependency before you can configure the ``cloudfront`` proxy client.
282+
283+
.. code-block:: yaml
284+
285+
# config/packages/fos_http_cache.yaml
286+
fos_http_cache:
287+
proxy_client:
288+
cloudfront:
289+
distribution_id: '<my-distribution-id>'
290+
configuration:
291+
accessKeyId: '<my-access-key-id>'
292+
accessKeySecret: '<my-access-key-secret>'
293+
294+
.. code-block:: yaml
295+
296+
# config/packages/fos_http_cache.yaml
297+
fos_http_cache:
298+
proxy_client:
299+
cloudfront:
300+
distribution_id: '<my-distribution-id>'
301+
client: '<my.custom.client>'
302+
303+
``distribution_id``
304+
"""""""""""""""""""
305+
306+
**type**: ``string``
307+
308+
Identifier for the CloudFront distribution you want to purge the cache for.
309+
310+
``configuration``
311+
"""""""""""""""""
312+
313+
**type**: ``array`` **default**: ``[]``
314+
315+
Configuration used to instantiate the `AsyncAws\CloudFront\CloudFrontClient` client. More information is available on
316+
the `AWS Async documentation_`. It can not be used with the ``client`` option.
317+
318+
``client``
319+
"""""""""""""""""
320+
321+
**type**: ``string`` **default**: ``null``
322+
323+
Service identifier of a `AsyncAws\CloudFront\CloudFrontClient` client. More information is available on the
324+
`AWS Async documentation_`. It can not be used with the ``configuration`` option.
325+
326+
.. _configuration_noop_proxy_client:
327+
279328
noop
280329
----
281330

@@ -328,3 +377,4 @@ bundle. Please refer to the :ref:`FOSHttpCache library’s documentation <foshtt
328377
for more information.
329378

330379
.. _xkey vmod: https://github.com/varnish/varnish-modules/blob/master/docs/vmod_xkey.rst
380+
.. _AWS Async documentation_: https://async-aws.com/configuration.html

Resources/doc/spelling_word_list.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ autoconfigured
66
backend
77
cacheable
88
cloudflare
9+
cloudfront
910
ETag
1011
friendsofsymfony
1112
github

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"sebastian/exporter": "^2.0"
5454
},
5555
"suggest": {
56+
"jean-beru/fos-http-cache-cloudfront": "To use CloudFront proxy",
5657
"sensio/framework-extra-bundle": "For Tagged Cache Invalidation",
5758
"symfony/expression-language": "For Tagged Cache Invalidation",
5859
"symfony/console": "To send invalidation requests from the command line"

src/DependencyInjection/Configuration.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use FOS\HttpCache\SymfonyCache\PurgeListener;
1616
use FOS\HttpCache\SymfonyCache\PurgeTagsListener;
1717
use FOS\HttpCache\TagHeaderFormatter\TagHeaderFormatter;
18+
use JeanBeru\HttpCacheCloudFront\Proxy\CloudFront;
1819
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
1920
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
2021
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
@@ -412,7 +413,7 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
412413
->arrayNode('proxy_client')
413414
->children()
414415
->enumNode('default')
415-
->values(['varnish', 'nginx', 'symfony', 'cloudflare', 'noop'])
416+
->values(['varnish', 'nginx', 'symfony', 'cloudflare', 'cloudfront', 'noop'])
416417
->info('If you configure more than one proxy client, you need to specify which client is the default.')
417418
->end()
418419
->arrayNode('varnish')
@@ -494,6 +495,29 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
494495
->end()
495496
->end()
496497

498+
->arrayNode('cloudfront')
499+
->info('Configure a client to interact with AWS cloudfront. You need to install jean-beru/fos-http-cache-cloudfront to work with cloudfront')
500+
->children()
501+
->scalarNode('distribution_id')
502+
->info('Identifier for your CloudFront distribution you want to purge the cache for')
503+
->end()
504+
->scalarNode('client')
505+
->info('AsyncAws\CloudFront\CloudFrontClient client to use')
506+
->defaultNull()
507+
->end()
508+
->variableNode('configuration')
509+
->defaultValue([])
510+
->info('Client configuration from https://async-aws.com/configuration.html')
511+
->end()
512+
->end()
513+
->validate()
514+
->ifTrue(function ($v) {
515+
return null !== $v['client'] && count($v['configuration']) > 0;
516+
})
517+
->thenInvalid('You can not set both cloudfront.client and cloudfront.configuration')
518+
->end()
519+
->end()
520+
497521
->booleanNode('noop')->end()
498522
->end()
499523
->validate()
@@ -512,7 +536,7 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
512536
throw new InvalidConfigurationException(sprintf('You can only set one of "http.servers" or "http.servers_from_jsonenv" but not both to avoid ambiguity for the proxy "%s"', $proxyName));
513537
}
514538

515-
if (!\in_array($proxyName, ['noop', 'default', 'symfony', 'cloudflare'])) {
539+
if (!\in_array($proxyName, ['noop', 'default', 'symfony', 'cloudflare', 'cloudfront'])) {
516540
if (!$arrayServersConfigured && !$jsonServersConfigured) {
517541
throw new InvalidConfigurationException(sprintf('The "http.servers" or "http.servers_from_jsonenv" section must be defined for the proxy "%s"', $proxyName));
518542
}
@@ -525,6 +549,12 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
525549
throw new InvalidConfigurationException('Either configure the "http.servers" or "http.servers_from_jsonenv" section or enable "proxy_client.symfony.use_kernel_dispatcher"');
526550
}
527551
}
552+
553+
if ('cloudfront' === $proxyName) {
554+
if (!class_exists(CloudFront::class)) {
555+
throw new InvalidConfigurationException('For the cloudfront proxy client, you need to install jean-beru/fos-http-cache-cloudfront. Class '.CloudFront::class.' does not exist.');
556+
}
557+
}
528558
}
529559

530560
return $config;

src/DependencyInjection/FOSHttpCacheExtension.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace FOS\HttpCacheBundle\DependencyInjection;
1313

14+
use AsyncAws\CloudFront\CloudFrontClient;
1415
use FOS\HttpCache\ProxyClient\HttpDispatcher;
1516
use FOS\HttpCache\ProxyClient\ProxyClient;
1617
use FOS\HttpCache\SymfonyCache\KernelDispatcher;
@@ -90,6 +91,8 @@ public function load(array $configs, ContainerBuilder $container)
9091
if ('noop' !== $defaultClient
9192
&& array_key_exists('base_url', $config['proxy_client'][$defaultClient])) {
9293
$generateUrlType = UrlGeneratorInterface::ABSOLUTE_PATH;
94+
} elseif ('cloudfront' === $defaultClient) {
95+
$generateUrlType = UrlGeneratorInterface::ABSOLUTE_PATH;
9396
} else {
9497
$generateUrlType = UrlGeneratorInterface::ABSOLUTE_URL;
9598
}
@@ -340,6 +343,9 @@ private function loadProxyClient(ContainerBuilder $container, XmlFileLoader $loa
340343
if (isset($config['cloudflare'])) {
341344
$this->loadCloudflare($container, $loader, $config['cloudflare']);
342345
}
346+
if (isset($config['cloudfront'])) {
347+
$this->loadCloudfront($container, $loader, $config['cloudfront']);
348+
}
343349
if (isset($config['noop'])) {
344350
$loader->load('noop.xml');
345351
}
@@ -471,6 +477,27 @@ private function loadCloudflare(ContainerBuilder $container, XmlFileLoader $load
471477
$loader->load('cloudflare.xml');
472478
}
473479

480+
private function loadCloudfront(ContainerBuilder $container, XmlFileLoader $loader, array $config)
481+
{
482+
if (null !== $config['client']) {
483+
$container->setAlias(
484+
'fos_http_cache.proxy_client.cloudfront.cloudfront_client',
485+
$config['client']
486+
);
487+
} else {
488+
$container->setDefinition(
489+
'fos_http_cache.proxy_client.cloudfront.cloudfront_client',
490+
new Definition(CloudFrontClient::class, [$config['configuration']])
491+
);
492+
}
493+
494+
$container->setParameter('fos_http_cache.proxy_client.cloudfront.options', [
495+
'distribution_id' => $config['distribution_id'],
496+
]);
497+
498+
$loader->load('cloudfront.xml');
499+
}
500+
474501
/**
475502
* @param array $config Configuration section for the tags node
476503
* @param string $client Name of the client used with the cache manager,
@@ -629,6 +656,10 @@ private function getDefaultProxyClient(array $config)
629656
return 'cloudflare';
630657
}
631658

659+
if (isset($config['cloudfront'])) {
660+
return 'cloudfront';
661+
}
662+
632663
if (isset($config['noop'])) {
633664
return 'noop';
634665
}

src/Resources/config/cloudfront.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="fos_http_cache.proxy_client.cloudfront"
9+
class="JeanBeru\HttpCacheCloudFront\Proxy\CloudFront"
10+
public="true">
11+
<argument type="service" id="fos_http_cache.proxy_client.cloudfront.cloudfront_client"/>
12+
<argument>%fos_http_cache.proxy_client.cloudfront.options%</argument>
13+
</service>
14+
</services>
15+
16+
</container>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
$container->loadFromExtension('fos_http_cache', [
13+
'proxy_client' => [
14+
'cloudfront' => [
15+
'distribution_id' => 'my_distribution',
16+
'configuration' => ['accessKeyId' => 'AwsAccessKeyId', 'accessKeySecret' => 'AwsAccessKeySecret'],
17+
],
18+
],
19+
]);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services">
3+
4+
<config xmlns="http://example.org/schema/dic/fos_http_cache">
5+
<proxy-client>
6+
<cloudfront distribution-id="my_distribution">
7+
<configuration>
8+
<accessKeyId>AwsAccessKeyId</accessKeyId>
9+
<accessKeySecret>AwsAccessKeySecret</accessKeySecret>
10+
</configuration>
11+
</cloudfront>
12+
</proxy-client>
13+
14+
</config>
15+
</container>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fos_http_cache:
2+
3+
proxy_client:
4+
cloudfront:
5+
distribution_id: 'my_distribution'
6+
configuration:
7+
accessKeyId: 'AwsAccessKeyId'
8+
accessKeySecret: 'AwsAccessKeySecret'

tests/Unit/DependencyInjection/ConfigurationTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use FOS\HttpCacheBundle\DependencyInjection\Configuration;
1515
use FOS\HttpCacheBundle\DependencyInjection\FOSHttpCacheExtension;
16+
use JeanBeru\HttpCacheCloudFront\Proxy\CloudFront;
1617
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionConfigurationTestCase;
1718
use Symfony\Component\Config\Definition\ConfigurationInterface;
1819
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
@@ -327,6 +328,60 @@ public function testSupportsCloudflare()
327328
}
328329
}
329330

331+
public function testSupportsCloudfront()
332+
{
333+
if (!class_exists(CloudFront::class)) {
334+
$this->markTestSkipped('jean-beru/fos-http-cache-cloudfront not available');
335+
}
336+
337+
$expectedConfiguration = $this->getEmptyConfig();
338+
$expectedConfiguration['proxy_client'] = [
339+
'cloudfront' => [
340+
'distribution_id' => 'my_distribution',
341+
'configuration' => ['accessKeyId' => 'AwsAccessKeyId', 'accessKeySecret' => 'AwsAccessKeySecret'],
342+
'client' => null,
343+
],
344+
];
345+
$expectedConfiguration['cache_manager']['enabled'] = 'auto';
346+
$expectedConfiguration['cache_manager']['generate_url_type'] = 'auto';
347+
$expectedConfiguration['tags']['enabled'] = 'auto';
348+
$expectedConfiguration['invalidation']['enabled'] = 'auto';
349+
350+
$formats = array_map(function ($path) {
351+
return __DIR__.'/../../Resources/Fixtures/'.$path;
352+
}, [
353+
'config/cloudfront.yml',
354+
'config/cloudfront.xml',
355+
'config/cloudfront.php',
356+
]);
357+
358+
foreach ($formats as $format) {
359+
$this->assertProcessedConfigurationEquals($expectedConfiguration, [$format]);
360+
}
361+
}
362+
363+
public function testCloudfrontConfigurationWithClientIsNotAllowed()
364+
{
365+
if (!class_exists(CloudFront::class)) {
366+
$this->markTestSkipped('jean-beru/fos-http-cache-cloudfront not available');
367+
}
368+
369+
$this->expectException(InvalidConfigurationException::class);
370+
$this->expectExceptionMessage('You can not set both cloudfront.client and cloudfront.configuration');
371+
372+
$params = $this->getEmptyConfig();
373+
$params['proxy_client'] = [
374+
'cloudfront' => [
375+
'distribution_id' => 'my_distribution',
376+
'configuration' => ['accessKeyId' => 'AwsAccessKeyId', 'accessKeySecret' => 'AwsAccessKeySecret'],
377+
'client' => 'my.client',
378+
],
379+
];
380+
381+
$configuration = new Configuration(false);
382+
(new Processor())->processConfiguration($configuration, ['fos_http_cache' => $params]);
383+
}
384+
330385
public function testEmptyServerConfigurationIsNotAllowed()
331386
{
332387
$this->expectException(InvalidConfigurationException::class);

0 commit comments

Comments
 (0)