From 1bbf49d7c1b6a26e8d22cf97f6eead1bfdd7f474 Mon Sep 17 00:00:00 2001 From: Christoph Reiter <reiter.christoph@gmail.com> Date: Mon, 11 Apr 2022 16:18:11 +0200 Subject: [PATCH] Add a health check which initializes all services In some cases where services can't be instantiated, either because the implementation is missing, or some dependency is missing, or there is a cyclic dep then things would fail only at the time the service is needed, for example in one specific request. To avoid missing such errors and catch them early add a health check which tries to build all services. This allows us to abort a deployment in case a service is broken. --- src/HealthCheck/Checks/SymfonyCheck.php | 30 ++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/HealthCheck/Checks/SymfonyCheck.php b/src/HealthCheck/Checks/SymfonyCheck.php index c43d2e4..71138ad 100644 --- a/src/HealthCheck/Checks/SymfonyCheck.php +++ b/src/HealthCheck/Checks/SymfonyCheck.php @@ -7,15 +7,20 @@ namespace Dbp\Relay\CoreBundle\HealthCheck\Checks; use Dbp\Relay\CoreBundle\HealthCheck\CheckInterface; use Dbp\Relay\CoreBundle\HealthCheck\CheckOptions; use Dbp\Relay\CoreBundle\HealthCheck\CheckResult; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; class SymfonyCheck implements CheckInterface { private $parameters; + /** @var ContainerInterface */ + private $container; - public function __construct(ParameterBagInterface $parameters) + public function __construct(ParameterBagInterface $parameters, ContainerInterface $container) { $this->parameters = $parameters; + $this->container = $container; } public function getName(): string @@ -23,6 +28,28 @@ class SymfonyCheck implements CheckInterface return 'core.symfony'; } + private function checkAllServices(): CheckResult + { + $result = new CheckResult('Check if all Symfony services can be initialized'); + $result->set(CheckResult::STATUS_SUCCESS); + + // This catches errors like unimplemented interfaces, cyclic dependencies and so on. + // Otherwise we would only get those errors when the services are actually needed, + // on specific requests/tasks at runtime. + $container = $this->container; + assert($container instanceof Container); + foreach ($container->getServiceIds() as $id) { + try { + $container->get($id); + } catch (\Throwable $e) { + $result->set(CheckResult::STATUS_FAILURE, $e->getMessage(), ['exception' => $e]); + break; + } + } + + return $result; + } + private function checkAppSecret(): CheckResult { $result = new CheckResult('APP_SECRET should be set'); @@ -54,6 +81,7 @@ class SymfonyCheck implements CheckInterface $results = []; $results[] = $this->checkAppSecret(); $results[] = $this->checkAppDebug(); + $results[] = $this->checkAllServices(); return $results; } -- GitLab