From ca553e4fb80d944b43b15f8233c0358906d50275 Mon Sep 17 00:00:00 2001 From: Uladzimir Tsykun Date: Sat, 2 Sep 2023 16:10:38 +0200 Subject: [PATCH] Added healthcheck endpoint --- config/packages/security.yaml | 1 + config/services.yaml | 1 + src/Controller/Api/ApiHealthController.php | 82 +++++++++++++++++++ src/DependencyInjection/Configuration.php | 2 +- src/DependencyInjection/PacketonExtension.php | 1 + src/EventListener/SubRepositoryListener.php | 1 + swagger/packages-api.yaml | 4 + 7 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/Controller/Api/ApiHealthController.php diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 4787510c..fbb9e327 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -57,6 +57,7 @@ security: - { path: ^/login$, roles: PUBLIC_ACCESS } - { path: (^(/login/|/oauth2/))+, roles: PUBLIC_ACCESS } - { path: ^/reset-password, roles: PUBLIC_ACCESS } + - { path: ^/api/healthz, roles: PUBLIC_ACCESS } # Packagist - { path: (^(/change-password|/profile|/logout|/subrepository/))+, roles: ROLE_USER } diff --git a/config/services.yaml b/config/services.yaml index 53d075a0..aea461fe 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -57,6 +57,7 @@ services: autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. bind: $redis: '@snc_redis.default' + Redis: '@snc_redis.default' $jwtTokenConfig: '%packeton_jws_config%' $jwtSignAlgorithm: '%packeton_jws_algo%' $mirrorRepoMetaDir: '%mirror_repos_meta_dir%' diff --git a/src/Controller/Api/ApiHealthController.php b/src/Controller/Api/ApiHealthController.php new file mode 100644 index 00000000..30bfcf2d --- /dev/null +++ b/src/Controller/Api/ApiHealthController.php @@ -0,0 +1,82 @@ + 'json'])] +class ApiHealthController extends AbstractController +{ + #[Route('/healthz', name: 'health', methods: ['GET'])] + public function health(): Response + { + if (false === $this->getParameter('packeton_health_check')) { + return new JsonResponse([], 404); + } + + $checks = [ + 'database:ping' => function() { + /** @var Connection $conn */ + $conn = $this->container->get(ManagerRegistry::class)->getConnection(); + $conn->executeQuery('SELECT 1'); + }, + 'redis:ping' => function() { + /** @var \Redis $redis */ + $redis = $this->container->get(\Redis::class); + $redis->get('packages-last-modify'); + }, + 'cache:ping' => function() { + $this->container->get(CacheInterface::class)->get('cache:ping', fn () => []); + }, + ]; + + $status = true; + $checksResult = []; + foreach ($checks as $name => $fn) { + $item = $checksResult[$name] = $this->checkHealth($fn); + $status = $status && ($item['status'] !== 'fail'); + } + + $result = [ + 'status' => $status ? 'pass' : 'fail', + 'checks' => $checksResult, + ]; + + return new Response(json_encode($result, 448), $status ? 200 : 500, ['Content-Type' => 'application/json']); + } + + protected function checkHealth(callable $check): array + { + $result = ['time' => (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d\TH:i:s\Z')]; + + try { + $start = microtime(true); + $result += $check() ?: []; + $result['status'] = 'pass'; + $ping = microtime(true) - $start; + + $result += ['observedValue' => (float)round(1000*$ping, 2), 'observedUnit' => 'ms']; + } catch (\Throwable $e) { + $result['status'] = 'fail'; + } + + return $result; + } + + public static function getSubscribedServices(): array + { + return parent::getSubscribedServices() + [ + \Redis::class, + ManagerRegistry::class, + CacheInterface::class + ]; + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 68365ac1..31ed1921 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -55,7 +55,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('content_type')->end() ->end() ->end() - + ->booleanNode('health_check')->defaultTrue()->end() ->booleanNode('archive') ->defaultFalse() ->end() diff --git a/src/DependencyInjection/PacketonExtension.php b/src/DependencyInjection/PacketonExtension.php index 3bc1f3a1..0dc240ac 100644 --- a/src/DependencyInjection/PacketonExtension.php +++ b/src/DependencyInjection/PacketonExtension.php @@ -74,6 +74,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('packeton_artifact_storage', $config['artifacts']['artifact_storage'] ?? null); $container->setParameter('packeton_artifact_types', $config['artifacts']['support_types'] ?? []); $container->setParameter('packeton_web_protection', $config['web_protection'] ?? null); + $container->setParameter('packeton_health_check', $config['health_check'] ?? true); $container->registerAttributeForAutoconfiguration(AsWorker::class, static function (ChildDefinition $definition, AsWorker $attribute) { $attributes = get_object_vars($attribute); diff --git a/src/EventListener/SubRepositoryListener.php b/src/EventListener/SubRepositoryListener.php index 428b5903..4a117916 100644 --- a/src/EventListener/SubRepositoryListener.php +++ b/src/EventListener/SubRepositoryListener.php @@ -25,6 +25,7 @@ class SubRepositoryListener 'logout' => 1, 'subrepository_switch' => 1, 'subrepository_switch_root' => 1, + 'api_health' => 1, ]; public function __construct( diff --git a/swagger/packages-api.yaml b/swagger/packages-api.yaml index a0099dda..08be5741 100644 --- a/swagger/packages-api.yaml +++ b/swagger/packages-api.yaml @@ -27,6 +27,10 @@ paths: required: true type: "string" + '/api/healthz': + get: + summary: 'Health check' + '/api/update-package': post: tags: [ Packages ]