diff --git a/composer.json b/composer.json index cab3600e05e3ce6ee225458c345d68cdd810a5b8..9f24c94d7144cb1344af0e76e3cdc9cd3a30ce77 100644 --- a/composer.json +++ b/composer.json @@ -25,15 +25,17 @@ "docs": "https://devops-tools.docs.lakedrops.com/composer/plugin/d4d/" }, "require": { - "ext-json": "*", "php": ">=7.2", + "ext-json": "*", "composer-plugin-api": "^1||^2", + "henrywhitaker3/healthchecks-io": "^1.0", "lakedrops/ahoy": "^1.5||dev-main", "lakedrops/composer-json-utils": "^2.0||dev-main", "lakedrops/docker-traefik": "^3.0||dev-main" }, "require-dev": { "composer/composer": "^1||^2", + "lakedrops/drupal-environment": "^3.0", "phpunit/phpunit": "^8.4" }, "minimum-stability": "dev", @@ -45,5 +47,10 @@ }, "extra": { "class": "LakeDrops\\Docker4Drupal\\Plugin" + }, + "config": { + "allow-plugins": { + "lakedrops/ahoy": true + } } } diff --git a/src/Handler.php b/src/Handler.php index 1aa0d84f10a93b7eb24732590e1c7436188bec03..e13c5699f8ac6a3473da28c9fbe341d21c8bdc60 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -2,6 +2,11 @@ namespace LakeDrops\Docker4Drupal; +use Henrywhitaker3\Healthchecks\Exceptions\HealthchecksAccountLimitReachedException; +use Henrywhitaker3\Healthchecks\Exceptions\HealthchecksFailureException; +use Henrywhitaker3\Healthchecks\Exceptions\HealthchecksUnauthorisedException; +use Henrywhitaker3\Healthchecks\Exceptions\HealthchecksUuidNotFoundException; +use Henrywhitaker3\Healthchecks\HealthchecksManager; use LakeDrops\Component\Composer\BaseHandler; use LakeDrops\DockerTraefik\Traefik; use LakeDrops\DrupalEnvironment\Handler as DrupalEnvironment; @@ -47,7 +52,7 @@ class Handler extends BaseHandler { $this->getDockerGateway() : $this->getLocalIpv4('docker0'), 'proxy' => ($this->isCiContext() || $this->isLocalDevMode()) ? - $this->getDockerProxy($projectname) : + $this->getDockerProxy() : FALSE, ], 'traefik' => [ @@ -514,8 +519,85 @@ class Handler extends BaseHandler { 'dest' => $projectRoot . '/backup', 'condition' => $this->config->readValue(['backup', 'enable']), ]; + + // Manage crontabs and optionally add them to heathcheck-io + $hj_api_url = getenv('HEALTHCHECK_API_URL'); + $hj_api_key = getenv('HEALTHCHECK_API_KEY'); + $hj_api_channels = getenv('HEALTHCHECK_API_CHANNELS'); + $hj_project = $this->config->readValue('projectname'); + $hj_branch = getenv('CI_COMMIT_BRANCH'); + $hj_checks = []; + $hj_manager = NULL; + + if (!empty($hj_api_url) && !empty($hj_api_key)) { + $hj_manager = new HealthchecksManager($hj_api_key, $hj_api_url); + try { + $hj_checks = $hj_manager->listChecks(); + } + catch (HealthchecksFailureException | HealthchecksUnauthorisedException $e) { + // Ignoring this for now. + } + } foreach ($this->config->readValue('crontabs') ?? [] as $user => $tasks) { - if (empty($tasks)) { + if (!is_array($tasks)) { + continue; + } + $activeTasks = []; + foreach ($tasks as $task) { + $disabled = !empty($task['disabled']); + $command = $task['command']; + if (isset($hj_manager)) { + unset($task['disabled'], $task['command']); + $task['channels'] = $hj_api_channels; + $task['tags'] = implode(' ', ['d4d', $hj_project, $hj_branch]); + + $check = NULL; + foreach ($hj_checks as $existing) { + $tags = explode(' ', $existing['tags']); + if ($task['name'] === $existing['name'] && in_array('d4d', $tags, TRUE) && in_array($hj_project, $tags, TRUE) && in_array($hj_branch, $tags, TRUE)) { + $check = $existing; + break; + } + } + if (!$check) { + if ($disabled) { + // This task is disabled. As it doesn't exist yet, nothing to do. + continue; + } + try { + $check = $hj_manager->createCheck($task); + } + catch (HealthchecksAccountLimitReachedException | HealthchecksUnauthorisedException $e) { + // Ignoring this for now. + } + } + else { + $changed = FALSE; + foreach ($task as $key => $value) { + if (!isset($check[$key]) || $check[$key] !== $value) { + $changed = TRUE; + } + } + if ($changed) { + $parts = explode('/', $check['ping_url']); + $uuid = array_pop($parts); + try { + $check = $hj_manager->updateCheck($uuid, $task); + } + catch (HealthchecksFailureException | HealthchecksUuidNotFoundException | HealthchecksAccountLimitReachedException | HealthchecksUnauthorisedException $e) { + // Ignoring this for now. + } + } + } + if ($check) { + $command .= ' && curl -fsS --retry 5 -o /dev/null ' . $check['ping_url']; + } + } + if (!$disabled) { + $activeTasks[] = $command; + } + } + if (empty($activeTasks)) { $files[$user] = [ 'dest' => $projectRoot . '/crontabs', 'delete' => TRUE, @@ -525,7 +607,7 @@ class Handler extends BaseHandler { $files[$user] = [ 'source' => 'crontabs/template.twig', 'dest' => $projectRoot . '/crontabs', - 'options' => $tasks, + 'options' => $activeTasks, ]; } } @@ -574,11 +656,9 @@ class Handler extends BaseHandler { } /** - * @param string $projectname - * * @return string */ - private function getDockerProxy(string $projectname): string { + private function getDockerProxy(): string { foreach ($this->readNetworkConfig()['Containers'] as $container) { if (isset($container['Name']) && in_array($container['Name'], ['traefik_traefik_1', 'traefik-traefik-1'])) { return explode('/', $container['IPv4Address'])[0]; @@ -623,7 +703,7 @@ class Handler extends BaseHandler { exec('docker container inspect ' . $id, $output); return json_decode(implode('', $output), TRUE)[0]; } - catch (Exception $ex) { + catch (\Exception $ex) { // Ignore. } return [ @@ -643,7 +723,7 @@ class Handler extends BaseHandler { exec('docker network inspect traefik-public', $output); return json_decode(implode('', $output), TRUE)[0]; } - catch (Exception $ex) { + catch (\Exception $ex) { // Ignore. } return [ diff --git a/templates/docker-compose.yml.twig b/templates/docker-compose.yml.twig index c2753050d875edb73db76095df89a19271076c96..8fab5098e0b6f6c40e69ff382a28c47c855464d8 100644 --- a/templates/docker-compose.yml.twig +++ b/templates/docker-compose.yml.twig @@ -21,12 +21,11 @@ services: MYSQL_USER: drupal MYSQL_PASSWORD: drupal MYSQL_LOG_CONSOLE: 0 -{% if drupal.live|default(0) %} volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro +{% if drupal.live|default(0) %} - {{ projectrootdb }}:/var/lib/mysql -{% if backup.enable|default(0) %} - - {{ projectrootbackup }}/db:/var/backups/mysql -{% endif %} {% endif %} {% if not drupal.live|default(0) %} @@ -39,6 +38,9 @@ services: MYSQL_USER: drupal MYSQL_PASSWORD: drupal MYSQL_LOG_CONSOLE: 0 + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro {% endif %} php: @@ -82,6 +84,8 @@ services: SSH_AUTH_SOCK: /ssh-agent {% endif %} volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - {{ projectroot }}:/var/www/html - {{ projectroot }}/drush:/etc/drush {% if drupal.live|default(0) %} @@ -120,6 +124,8 @@ services: SSH_AUTH_SOCK: /ssh-agent {% endif %} volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - {{ projectroot }}:/var/www/html - {{ projectroot }}/drush:/etc/drush - {{ projectrootfiles }}:/data/default @@ -166,6 +172,8 @@ services: {{ webserver.type|upper }}_BACKEND_HOST: php {{ webserver.type|upper }}_SERVER_ROOT: /var/www/html/{{ webRoot }}/ volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - {{ projectroot }}:/var/www/html {% if drupal.live|default(0) %} - {{ projectrootfiles }}:/data/default @@ -214,6 +222,9 @@ services: VARNISH_SECRET: secret VARNISH_BACKEND_HOST: {{ webserver.type }} VARNISH_BACKEND_PORT: 80 + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro labels: traefik.enable: 'true' traefik.docker.network: traefik-public @@ -223,10 +234,12 @@ services: redis: image: 'wodby/redis:{{ redis.version }}' -{% if drupal.live|default(0) %} - restart: unless-stopped volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro +{% if drupal.live|default(0) %} - {{ projectrootredis }}:/data + restart: unless-stopped {% endif %} {% if not drupal.live|default(0) %} @@ -238,6 +251,9 @@ services: - default environment: ADMINER_SALT: adminer-salt + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro labels: traefik.enable: 'true' traefik.docker.network: traefik-public @@ -257,6 +273,9 @@ services: PMA_PASSWORD: drupal PHP_UPLOAD_MAX_FILESIZE: 1G PHP_MAX_INPUT_VARS: 1G + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro labels: traefik.enable: 'true' traefik.docker.network: traefik-public @@ -275,6 +294,9 @@ services: - default environment: SOLR_HEAP: 1024m + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro labels: traefik.enable: 'true' traefik.docker.network: traefik-public @@ -295,6 +317,8 @@ services: MH_OUTGOING_SMTP: /test/mhout.json privileged: true volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - {{ projectroot }}/tests/mhout.json:/test/mhout.json labels: traefik.enable: 'true' @@ -325,6 +349,8 @@ services: traefik.http.services.{{ projectname }}_nodejs.loadbalancer.server.port: 8080 traefik.http.routers.nodejs-{{ projectname }}.rule: Host(`nodejs-{{ projectdomain }}`) volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - {{ projectroot }}/{{ node.path }}:/app command: sh -c 'npm install && npm run start' @@ -342,6 +368,8 @@ services: expose: - '3000' volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - {{ projectroot }}/{{ node.path }}:/app command: sh -c 'npm install && npm run start' {% endif %} @@ -352,6 +380,9 @@ services: {% if drupal.live|default(0) %} restart: unless-stopped {% endif %} + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro {% endif %} {% if rsyslog.enable %} @@ -360,6 +391,9 @@ services: {% if drupal.live|default(0) %} restart: unless-stopped {% endif %} + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro {% endif %} {% if athenapdf.enable %} @@ -375,6 +409,9 @@ services: WEAVER_MAX_CONVERSION_QUEUE: 50 WEAVER_WORKER_TIMEOUT: 90 WEAVER_CONVERSION_FALLBACK: false + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro {% endif %} {% if blackfire.enable and not drupal.live|default(0) %} @@ -383,6 +420,9 @@ services: environment: BLACKFIRE_SERVER_ID: '{{ blackfire.id }}' BLACKFIRE_SERVER_TOKEN: '{{ blackfire.token }}' + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro {% endif %} {% if webgrind.enable and not drupal.live|default(0) %} @@ -400,6 +440,8 @@ services: traefik.http.services.{{ projectname }}_webgrind.loadbalancer.server.port: 8080 traefik.http.routers.webgrind-{{ projectname }}.rule: Host(`webgrind-{{ projectdomain }}`) volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - {{ projectroot }}/files:/mnt/files {% endif %} {% if selenium.enable and not drupal.live|default(0) %} @@ -407,6 +449,8 @@ services: hub: image: 'elgalu/selenium' volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - /dev/shm:/dev/shm privileged: true environment: @@ -423,6 +467,8 @@ services: depends_on: - hub volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - /dev/shm:/dev/shm privileged: true environment: @@ -457,6 +503,9 @@ services: ports: - 9200 - 9300 + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro {% endif %} {% if drupal.live|default(0) %} {% if backup.enable|default(0) %} @@ -465,14 +514,14 @@ services: image: 'b3vis/borgmatic:{{ backup.version }}' restart: unless-stopped volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - {{ projectrootfiles }}:/mnt/source:ro - {{ projectrootbackup }}/files:/mnt/borg-repository - {{ projectroot }}/backup:/etc/borgmatic.d/ - {{ projectrootbackup }}/config:/root/.config/borg - {{ projectrootbackup }}/ssh:/root/.ssh - {{ projectrootbackup }}/cache:/root/.cache/borg - environment: - TZ: Europe/Berlin env_file: ../backup/.env {% endif %} {% endif %}