diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c40f7ab..6560d8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ name: CI env: SYMFONY_DEPRECATIONS_HELPER: disabled BROWSERTEST_OUTPUT_DIRECTORY: 'public/sites/simpletest' + COMPOSER_DEPENDENCIES: drupal/redirect drupal/helfi_tunnistamo drupal/simple_sitemap drupal/varnish_purger jobs: tests: runs-on: ubuntu-latest @@ -56,7 +57,7 @@ jobs: # phpunit. Copy .git folder manually so codecov can generate line by # line coverage. cp -r $GITHUB_WORKSPACE/.git $MODULE_FOLDER/ - composer require drupal/redirect drupal/helfi_tunnistamo drupal/simple_sitemap -W + composer require $COMPOSER_DEPENDENCIES -W - name: Install Drupal working-directory: ${{ env.DRUPAL_ROOT }} diff --git a/config/optional/purge.logger_channels.yml b/config/optional/purge.logger_channels.yml index 6f16d50..d2acba4 100644 --- a/config/optional/purge.logger_channels.yml +++ b/config/optional/purge.logger_channels.yml @@ -21,3 +21,15 @@ channels: - 0 - 2 - 3 + - + id: purger_varnish_varnish_purge_all + grants: + - 0 + - 2 + - 3 + - + id: purger_varnish_assets + grants: + - 0 + - 2 + - 3 diff --git a/config/optional/purge.plugins.yml b/config/optional/purge.plugins.yml index 9b35555..fd28365 100644 --- a/config/optional/purge.plugins.yml +++ b/config/optional/purge.plugins.yml @@ -1,12 +1,16 @@ purgers: - - order_index: 2 instance_id: default plugin_id: varnish + order_index: 2 - - order_index: 3 instance_id: varnish_purge_all plugin_id: varnish + order_index: 3 + - + instance_id: assets + plugin_id: varnish + order_index: 4 processors: - plugin_id: drush_purge_queue_work @@ -14,6 +18,9 @@ processors: - plugin_id: cron status: true + - + plugin_id: drush_purge_invalidate + status: true queuers: - plugin_id: purge_ui_block_queuer diff --git a/config/optional/varnish_purger.settings.assets.yml b/config/optional/varnish_purger.settings.assets.yml new file mode 100644 index 0000000..6370657 --- /dev/null +++ b/config/optional/varnish_purger.settings.assets.yml @@ -0,0 +1,27 @@ +langcode: en +status: true +dependencies: { } +id: assets +name: Assets +invalidationtype: regex +hostname: localhost +port: 6081 +path: '/[invalidation:expression]' +request_method: BAN +scheme: http +verify: '1' +headers: + - + field: X-VC-Purge-Method + value: regex + - + field: Host + value: localhost +body: null +body_content_type: null +runtime_measurement: true +timeout: 1.0 +connect_timeout: 1.0 +cooldown_time: 0.0 +max_requests: 100 +http_errors: true diff --git a/helfi_proxy.install b/helfi_proxy.install index 5a9d8e3..ce8bdc3 100644 --- a/helfi_proxy.install +++ b/helfi_proxy.install @@ -5,7 +5,7 @@ * Contains helfi_proxy installation procedure. */ -declare(strict_types = 1); +declare(strict_types=1); /** * Implements hook_install(). @@ -25,3 +25,41 @@ function helfi_proxy_install() { function helfi_proxy_update_9001() : void { helfi_proxy_install(); } + +/** + * Enable asset purge configuration. + */ +function helfi_proxy_update_9002() : void { + if (!\Drupal::moduleHandler()->moduleExists('varnish_purger')) { + return; + } + /** @var \Drupal\Core\Config\ConfigInstallerInterface $configInstaller */ + $configInstaller = \Drupal::service('config.installer'); + $configInstaller->installDefaultConfig('module', 'helfi_proxy'); + + // Re-installing the default configuration does not install purge + // configuration for some reason. + $plugins = \Drupal::configFactory()->getEditable('purge.plugins'); + $purgers = $plugins->get('purgers'); + + if (!array_filter($purgers, fn (array $item) => $item['instance_id'] === 'assets')) { + $purgers[] = [ + 'instance_id' => 'assets', + 'plugin_id' => 'varnish', + 'order_index' => 4, + ]; + $plugins->set('purgers', $purgers) + ->save(); + } + $loggers = \Drupal::configFactory()->getEditable('purge.logger_channels'); + $channels = $loggers->get('channels'); + + if (!array_filter($channels, fn (array $item) => $item['id'] === 'purger_varnish_assets')) { + $channels[] = [ + 'id' => 'purger_varnish_assets', + 'grants' => [0, 2, 3], + ]; + } + $loggers->set('channels', $channels) + ->save(); +} diff --git a/helfi_proxy.services.yml b/helfi_proxy.services.yml index 7a96b54..84b73a0 100644 --- a/helfi_proxy.services.yml +++ b/helfi_proxy.services.yml @@ -1,5 +1,6 @@ parameters: helfi_proxy.valid_origin_domains: + - hel.ninja - hel.fi - docker.so services: @@ -45,9 +46,3 @@ services: - '%helfi_proxy.valid_origin_domains%' tags: - { name: event_subscriber } - - helfi_proxy.asset.css.optimizer: - public: false - class: Drupal\helfi_proxy\Asset\CssOptimizer - decorates: asset.css.optimizer - arguments: ['@config.factory', '@file_url_generator'] diff --git a/src/ActiveSitePrefix.php b/src/ActiveSitePrefix.php index b9ed358..de9e73a 100644 --- a/src/ActiveSitePrefix.php +++ b/src/ActiveSitePrefix.php @@ -1,6 +1,6 @@ assetPath = $configFactory->get('helfi_proxy.settings') - ->get('asset_path'); - - parent::__construct($fileUrlGenerator); - } - - /** - * {@inheritdoc} - */ - public function rewriteFileURI($matches) : string { // phpcs:ignore - if (!$this->assetPath) { - return parent::rewriteFileURI($matches); - } - // Prefix with base and remove '../' segments where possible. - $path = $this->rewriteFileURIBasePath . $matches[1]; - $last = ''; - while ($path != $last) { - $last = $path; - $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path); - } - $path = $this->fileUrlGenerator->transformRelative( - $this->fileUrlGenerator->generateAbsoluteString($path) - ); - - // Prefix with /{asset-path}. - return sprintf('url(/%s/%s)', trim($this->assetPath, '/'), ltrim($path, '/')); - } - -} diff --git a/src/Cache/Context/SitePrefixCacheContext.php b/src/Cache/Context/SitePrefixCacheContext.php index df5c756..e497ac6 100644 --- a/src/Cache/Context/SitePrefixCacheContext.php +++ b/src/Cache/Context/SitePrefixCacheContext.php @@ -1,6 +1,6 @@ proxyManager->isConfigured(ProxyManagerInterface::ASSET_PATH)) { + return; + } + $assetPath = $this->proxyManager->getConfig(ProxyManagerInterface::ASSET_PATH); + + $queuer = $this->queuers->get('helfi_proxy_queue_everything'); + // Purge all assets from Varnish cache. For example: /liikenne-assets/* + // on Liikenne project. + $this->queue->add($queuer, [ + $this->invalidationFactory->get('regex', ltrim($assetPath, '/')), + ]); + } + +} diff --git a/src/EventSubscriber/RobotsResponseSubscriber.php b/src/EventSubscriber/RobotsResponseSubscriber.php index e57d5c2..7a1c85b 100644 --- a/src/EventSubscriber/RobotsResponseSubscriber.php +++ b/src/EventSubscriber/RobotsResponseSubscriber.php @@ -1,6 +1,6 @@ addArgument(new Reference('helfi_proxy.active_prefix')); } + if (isset($modules['varnish_purger'])) { + $container->register('helfi_proxy.deploy_hook', DeploySubscriber::class) + ->addTag('event_subscriber') + ->addArgument(new Reference('helfi_proxy.proxy_manager')) + ->addArgument(new Reference('purge.invalidation.factory')) + ->addArgument(new Reference('purge.queuers')) + ->addArgument(new Reference('purge.queue')); + } + } } diff --git a/src/PathProcessor/SitePrefixPathProcessor.php b/src/PathProcessor/SitePrefixPathProcessor.php index 37f084f..63b95c0 100644 --- a/src/PathProcessor/SitePrefixPathProcessor.php +++ b/src/PathProcessor/SitePrefixPathProcessor.php @@ -1,6 +1,6 @@ installEntitySchema('varnishpurgersettings'); + $this->installConfig(['helfi_proxy']); + // Purge plugin configuration is not installed automatically. + $this->config('purge.plugins')->set('purgers', [ + [ + 'instance_id' => 'assets', + 'plugin_id' => 'varnish', + 'order_index' => 1, + ], + ])->save(); + $this->container->get('kernel')->rebuildContainer(); + } + + /** + * Tests deploy hooks without a configured asset path. + */ + public function testAssetPurge() : void { + $eventDispatcher = $this->container->get('event_dispatcher'); + $eventDispatcher->dispatch(new PostDeployEvent()); + /** @var \Drupal\purge\Plugin\Purge\Queue\QueueServiceInterface $queuer */ + $queuer = $this->container->get('purge.queue'); + // Make sure queue has no items when the asset path is not configured. + $this->assertEquals(0, $queuer->numberOfItems()); + + $this->config('helfi_proxy.settings') + ->set('asset_path', 'test-assets') + ->save(); + // Make sure the asset path gets invalidated when configured. + $eventDispatcher->dispatch(new PostDeployEvent()); + $this->assertEquals(1, $queuer->numberOfItems()); + /** @var \Drupal\purge\Plugin\Purge\Invalidation\RegularExpressionInvalidation $item */ + $item = $queuer->claim()[0]; + $this->assertInstanceOf(RegularExpressionInvalidation::class, $item); + $this->assertEquals('test-assets', $item->getExpression()); + } + +} diff --git a/tests/src/Kernel/Assets/CssOptimizerTest.php b/tests/src/Kernel/Assets/CssOptimizerTest.php new file mode 100644 index 0000000..014faf9 --- /dev/null +++ b/tests/src/Kernel/Assets/CssOptimizerTest.php @@ -0,0 +1,63 @@ +container->get('asset.css.optimizer'); + $css_asset = [ + 'group' => -100, + 'type' => 'file', + 'weight' => 0.013, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => $this->getFixturePath('helfi_proxy', 'test.css'), + ]; + $fixturePath = $assetPath . $this->getExtensionPathResolver()->getPath('module', 'helfi_proxy') . '/tests/fixtures/path/to/something.png'; + $optimized = $optimizer->optimize($css_asset); + $this->assertStringContainsString('url(' . $fixturePath . ')', $optimized); + } + + /** + * Tests asset optimize without a configured asset path. + */ + public function testRewriteWithoutAssetPath() : void { + $this->assertOptimizedPath('/'); + } + + /** + * Make sure CSS paths are rewritten to start with the asset path. + */ + public function testRewriteWithAssetPath() : void { + $this->config('helfi_proxy.settings') + ->set('asset_path', 'test-assets') + ->save(); + $this->assertOptimizedPath('/test-assets/'); + } + +} diff --git a/tests/src/Kernel/CorsResponseSubscriberTest.php b/tests/src/Kernel/CorsResponseSubscriberTest.php index d48b22d..47f9be1 100644 --- a/tests/src/Kernel/CorsResponseSubscriberTest.php +++ b/tests/src/Kernel/CorsResponseSubscriberTest.php @@ -1,6 +1,6 @@