Skip to content
Snippets Groups Projects
Handler.php 26.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • jurgenhaas's avatar
    jurgenhaas committed
    <?php
    
    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\Healthchecks;
    
    use Henrywhitaker3\Healthchecks\HealthchecksManager;
    
    use LakeDrops\Component\Composer\BaseHandler;
    
    use LakeDrops\DockerTraefik\Traefik;
    
    use LakeDrops\DrupalEnvironment\Handler as DrupalEnvironment;
    
    jurgenhaas's avatar
    jurgenhaas committed
    use Symfony\Component\Filesystem\Filesystem;
    
    jurgenhaas's avatar
    jurgenhaas committed
    
    
    jurgenhaas's avatar
    jurgenhaas committed
    /**
     * Class Handler.
     *
     * @package LakeDrops\Docker4Drupal
     */
    
    class Handler extends BaseHandler {
    
    jurgenhaas's avatar
    jurgenhaas committed
    
    
      public function configId(): string {
        return 'docker4drupal';
      }
    
      /**
       * {@inheritdoc}
       */
      protected function configDefault(): array {
    
        $dockerImageProxy = $this->env->receiveGlobal('docker_image_prefix', '', '');
        if (!empty($dockerImageProxy)) {
          $dockerImageProxy = str_replace('//', '/', $dockerImageProxy . '/');
        }
    
        $projectname = getenv('COMPOSE_PROJECT_NAME');
    
    jurgenhaas's avatar
    jurgenhaas committed
        if (empty($projectname)) {
          $projectname = str_replace([' ', '-', '_', '.'], '', basename(getcwd()));
    
          $this->env->put('COMPOSE_PROJECT_NAME', $projectname);
        }
    
    
        return [
    
          'projectname' => $projectname,
    
    jurgenhaas's avatar
    jurgenhaas committed
          'staging' => FALSE,
    
    jurgenhaas's avatar
    jurgenhaas committed
          'basicauth' => [
    
            'enable' => FALSE,
    
    jurgenhaas's avatar
    jurgenhaas committed
            'user' => '',
            'pass' => '',
            'code' => '',
    
    jurgenhaas's avatar
    jurgenhaas committed
          ],
    
          'ci_home' => '/home/gitlab-runner',
          'docker0' => [
            'ip' => ($this->isCiContext() || $this->isLocalDevMode()) ?
              $this->getDockerGateway() :
              $this->getLocalIpv4('docker0'),
    
            'proxy' => ($this->isCiContext() || $this->isLocalDevMode()) ?
    
          'traefik' => [
            'domain' => $this->env->receive('traefik_domain', '', 'docker.localhost'),
    
            'usessl' => $this->env->receive('traefik_usessl', '', '0'),
    
            'port' => $this->env->receive('traefik_port', '', '8000'),
            'ports' => $this->env->receive('traefik_ports', '', '8443'),
            'cert' => $this->env->receive('traefik_cert', '', 'fullchain.pem'),
            'key' => $this->env->receive('traefik_key', '', 'privkey.pem'),
    
            'portainer' => $this->env->receive('traefik_portainer', '', '0'),
    
    jurgenhaas's avatar
    jurgenhaas committed
            'hub_token' => $this->env->receive('traefik_hub_token', '', ''),
    
          'live' => [
            'root' => '',
            'uri' => '',
            'host' => '',
            'user' => $this->env->receive('live_host_username', 'Remote username for host of the live site', getenv('USER')),
          ],
          'drush' => [
            'sql' => [
              'tables' => [
                'structure' => [
                  'cache',
                  'cachetags',
                  'cache_*',
                  'history',
                  'search_*',
                  'sessions',
                  'watchdog',
                ],
                'skip' => [
                  'migration_*',
                ],
              ],
            ],
          ],
          'drupal' => [
    
            'version' => $this->env->receiveGlobal('PHP_VERSION', 'PHP version', '7.4'),
    
            'xdebug' => $this->env->receiveGlobal('PHP_DEBUG', 'PHP debug', '0'),
    
            'coverage' => $this->env->receiveGlobal('PHP_COVERAGE', 'PHP coverage', '0'),
    
            'related_subdomains' => [],
    
            'version' => '10.6',
    
          'webserver' => [
            'type' => 'apache',
            'overwriteconfig' => FALSE,
          ],
    
    jurgenhaas's avatar
    jurgenhaas committed
            'enable' => 1,
    
            'host' => $this->env->receiveGlobal('MAILHOG_HOST', 'MailHog Host', 'smtp.freesmtpservers.com'),
            'port' => $this->env->receiveGlobal('MAILHOG_PORT', 'MailHog Port', '25'),
    
            'username' => $this->env->receiveGlobal('MAILHOG_USERNAME', 'MailHog Username'),
            'password' => $this->env->receiveGlobal('MAILHOG_PASSWORD', 'MailHog Password'),
    
            'mechanism' => $this->env->receiveGlobal('MAILHOG_MECHANISM', 'MailHog Auth Mechanism', 'NONE'),
    
          'varnish' => [
            'enable' => 0,
          ],
          'redis' => [
    
          ],
          'dbbrowser' => [
            'type' => 'pma',
          ],
          'solr' => [
            'enable' => 0,
    
          ],
          'node' => [
            'enable' => 0,
            'key' => '',
            'path' => '',
          ],
          'memcached' => [
            'enable' => 0,
          ],
          'rsyslog' => [
            'enable' => 0,
          ],
          'athenapdf' => [
            'enable' => 0,
            'key' => '',
          ],
          'blackfire' => [
            'enable' => 0,
            'id' => '',
            'token' => '',
          ],
          'webgrind' => [
            'enable' => 0,
          ],
    
          'selenium' => [
            'enable' => 0,
          ],
          'elasticsearch' => [
            'enable' => 0,
          ],
    
          'wkhtmltox' => [
            'enable' => 0,
          ],
    
          'backstop' => $this->backstopDefaults(),
    
    jurgenhaas's avatar
    jurgenhaas committed
          'crontabs' => [
    
    jurgenhaas's avatar
    jurgenhaas committed
          ],
          'backup' => [
    
            'enable' => FALSE,
    
    jurgenhaas's avatar
    jurgenhaas committed
            'version' => 'base-1.2.0-1.6.0',
    
            'crontime' => '50 */6 * * *',
    
            'crontimecheck' => '30 23 1 * *',
    
    jurgenhaas's avatar
    jurgenhaas committed
            'remoterepo' => FALSE,
            'retention' => [
              'hourly' => 2,
              'daily' => 7,
              'weekly' => 8,
              'monthly' => 12,
              'yearly' => 30,
            ],
          ],
    
      }
    
      /**
       * {@inheritdoc}
       */
      protected function postInit(): void {
        $this->env->put('PHP_VERSION', $this->config->readValue(['php', 'version']), TRUE);
    
    
        if ($this->isCiContext() || $this->isLocalDevMode()) {
          $projectRoot = $this->getDockerMountSource(getenv('CI_PROJECT_DIR'));
        }
        else {
          $projectRoot = getcwd();
        }
    
        $php = $this->config->readValue('php');
        $traefik = $this->config->readValue('traefik');
    
    
        // Check if SSH auth sockets are supported.
        $ssh_auth_sock = getenv('SSH_AUTH_SOCK');
    
        $php['ssh'] = !empty($ssh_auth_sock);
        if ($php['ssh']) {
          $php['ssh_auth_sock'] = ($this->isCiContext() || $this->isLocalDevMode()) ?
    
            $this->getDockerMountSource('/ssh-agent') :
            '$SSH_AUTH_SOCK';
        }
    
        $this->config->setValue('php', $php, FALSE);
        $this->config->setValue('projectroot', $projectRoot, FALSE);
        $this->config->setValue('projectdomain', $this->config->readValue('projectname') . '.' . $traefik['domain'], FALSE);
        $this->config->setValue('projectprotocol', 'http' . ($traefik['usessl'] ? 's' : ''), FALSE);
        $projectport = '';
        if ($traefik['usessl'] && (int) $traefik['ports'] !== 443) {
          $projectport = ':' . $traefik['ports'];
    
        elseif (!$traefik['usessl'] && (int) $traefik['port'] !== 80) {
          $projectport = ':' . $traefik['port'];
    
        $this->config->setValue('projectport', $projectport, FALSE);
        $relatedprojectdomains = [];
        foreach ($php['related_subdomains'] as $related_subdomain) {
          $relatedprojectdomains[] = $related_subdomain . '.' . $traefik['domain'];
        }
        $this->config->setValue('relatedprojectdomains', $relatedprojectdomains, FALSE);
    
    
        if ($alerta_api_key = getenv('ALERTA_APIKEY')) {
          $alerta = [
            'apikey' => $alerta_api_key,
          ];
          if ($value = getenv('ALERTA_ENVIRONMENT')) {
            $alerta['environment'] = $value;
          }
          if ($value = getenv('ALERTA_PROJECT_ID')) {
            $alerta['project_id'] = $value;
          }
          $this->config->setValue('alerta', $alerta, FALSE);
        }
    
    jurgenhaas's avatar
    jurgenhaas committed
      /**
       * Configure Drupal Project for Docker.
       *
    
    jurgenhaas's avatar
    jurgenhaas committed
       *   Whether to overwrite existing config files.
    
    jurgenhaas's avatar
    jurgenhaas committed
       */
    
      public function configureProject(bool $overwrite = FALSE): void {
    
    jurgenhaas's avatar
    jurgenhaas committed
    
    
    jurgenhaas's avatar
    jurgenhaas committed
        // We only do the fancy stuff for developers.
    
        if (!$this->isDevMode()) {
    
    jurgenhaas's avatar
    jurgenhaas committed
          return;
        }
    
    
        // Configure Drupal environment if avaiable.
        if ($this->getPackage('lakedrops/drupal-environment')) {
          $handler = new DrupalEnvironment($this->composer, $this->io);
          $handler->setupLakeDropsProject();
    
          // Update config for production build.
          if (getenv('LAKEDROPS_BUILD_NG') === 'yes') {
            $config = $handler->getConfig();
    
    jurgenhaas's avatar
    jurgenhaas committed
            $isStaging = !in_array(getenv('CI_COMMIT_REF_SLUG'), ['master', 'main'], TRUE);
    
            $root = '/drupal/' . getenv('CI_PROJECT_ID') . '/' . getenv('CI_COMMIT_BRANCH');
            $drupal = $this->config->readValue('drupal');
            $drupal['live'] = $config->readValue('live');
            $traefik = $this->config->readValue('traefik');
            $traefik['usessl'] = 1;
            $traefik['ports'] = 443;
    
            $crontabs = $this->config->readValue('crontabs');
    
    jurgenhaas's avatar
    jurgenhaas committed
            $crontabs['enable'] = (array_sum(array_map('count', $crontabs)) > 0);
    
              if ($this->config->readValue(['backup', 'enable'])) {
                $crontabs['borgmatic']['Backup'] = [
                  'schedule' => $this->config->readValue(['backup', 'crontime']),
                  'command' => 'backup',
                ];
                $crontabs['borgmatic']['Check backup'] = [
                  'schedule' => $this->config->readValue(['backup', 'crontimecheck']),
                  'command' => 'check',
                ];
              }
    
              $crontabs['www-data']['MySQL backup'] = [
                'schedule' => '5 0 * * *',
                'command' => 'cd /var/www/html && /usr/local/bin/drush sql:dump --result-file=/var/backups/mysql/drupal.sql',
              ];
    
    jurgenhaas's avatar
    jurgenhaas committed
              $crontabs['enable'] = TRUE;
    
    jurgenhaas's avatar
    jurgenhaas committed
              $crontabs['enable_mysql_backup'] = TRUE;
    
            $this->config->setValue('crontabs', $crontabs, FALSE);
    
    jurgenhaas's avatar
    jurgenhaas committed
            $overwriteConfig = [
    
    jurgenhaas's avatar
    jurgenhaas committed
              'docker0' => [
                'ip' => 'TRAEFIK-IP-PLACEHOLDER',
                'proxy' => 'TRAEFIK-IP-PLACEHOLDER',
              ],
    
              'drupal' => $drupal,
              'traefik' => $traefik,
              'projectroot' => $root . '/app',
    
    jurgenhaas's avatar
    jurgenhaas committed
              'projectrootbackup' => $root . '/backup',
    
              'projectrootdb' => $root . '/db',
              'projectrootfiles' => $root . '/files',
    
    jurgenhaas's avatar
    jurgenhaas committed
              'projectrootredis' => $root . '/redis',
    
    jurgenhaas's avatar
    jurgenhaas committed
              'projectname' => getenv('PROJECT_NAME') . '_' . getenv('CI_COMMIT_REF_SLUG'),
    
    jurgenhaas's avatar
    jurgenhaas committed
              'projectdomain' => $this->config->readValue('domain') ?? '',
    
              'projectprotocol' => 'https',
              'projectport' => '',
    
    jurgenhaas's avatar
    jurgenhaas committed
              'extradomains' => $this->config->readValue('aliases') ?? [],
    
    jurgenhaas's avatar
    jurgenhaas committed
            foreach ($overwriteConfig as $key => $value) {
    
              $this->config->setValue($key, $value, FALSE);
            }
          }
        }
    
    
    jurgenhaas's avatar
    jurgenhaas committed
        $fs = new Filesystem();
        $installationManager = $this->composer->getInstallationManager();
    
    
        $webRoot = $this->config->readValue('webroot');
        if ($webRoot !== NULL) {
          if (!$fs->exists($webRoot)) {
    
            return;
          }
        }
        else {
          $drupalCorePackage = $this->getDrupalCorePackage();
          if (!$drupalCorePackage) {
            // We are called too early, Drupal core is not available yet.
            return;
          }
          $corePath = $installationManager->getInstallPath($drupalCorePackage);
    
    jurgenhaas's avatar
    jurgenhaas committed
          // Directory where Drupal's index.php is located.
    
          $webRoot = dirname($corePath);
    
        $this->config->setValue('webRoot', $webRoot, FALSE);
    
    jurgenhaas's avatar
    jurgenhaas committed
    
    
    jurgenhaas's avatar
    jurgenhaas committed
        // Directory where the root project is being created.
    
    jurgenhaas's avatar
    jurgenhaas committed
        $projectRoot = getcwd();
    
    jurgenhaas's avatar
    jurgenhaas committed
        // Directory where this plugin is being installed.
    
    jurgenhaas's avatar
    jurgenhaas committed
        $pluginRoot = $installationManager->getInstallPath($this->getPackage('lakedrops/docker4drupal'));
    
    
        // If the d8-project-scaffold or d9-project-scaffold  plugin is present we
        // only execute this one if $force is TRUE. This way we can make sure that
        // we get executed after d8-project-scaffold or d9-project-scaffold.
    
        $settingsPath = $webRoot . '/sites/default';
    
        if ($this->getPackage('lakedrops/d8-project-scaffold') ||
          $this->getPackage('lakedrops/d9-project-scaffold') ||
          $this->getPackage('lakedrops/drupal-environment') ||
          $this->getPackage('lakedrops/drupal-development-environment')) {
    
          if (!$fs->exists($projectRoot . '/settings/default')) {
            return;
          }
          $settingsPath = 'settings/default';
        }
    
    
    jurgenhaas's avatar
    jurgenhaas committed
        // Provide all the required files.
    
        $orig_ignored = FALSE;
    
        foreach ($this->getFiles($projectRoot, $webRoot, $settingsPath) as $template => $def) {
    
          if (isset($def['condition']) && !$def['condition']) {
            continue;
          }
    
          if (!$fs->exists($def['dest'])) {
            $fs->mkdir($def['dest']);
          }
    
          $filename = $this->config->render($template, $template);
    
          $file = $def['dest'] . '/' . $filename;
    
          if (!empty($def['delete'])) {
            if ($fs->exists($file)) {
              $fs->remove($file);
            }
            continue;
          }
    
          if (($overwrite && empty($def['add2git'])) || !$fs->exists($file)) {
    
    jurgenhaas's avatar
    jurgenhaas committed
            $source = isset($def['source']) && is_file($pluginRoot . '/templates/' . $def['source']) ?
              $pluginRoot . '/templates/' . $def['source'] :
              $pluginRoot . '/templates/' . ($def['source'] ?? '') . $template . '.twig';
            if (isset($def['options'])) {
              $this->config->setValue('loopoptions', $def['options'], FALSE);
            }
            $rendered = $this->config->render($filename, file_get_contents($source));
    
            $extraOptions = $this->config->readValue($filename);
            if (!empty($def['add2yaml']) && $extraOptions !== NULL) {
    
              /** @noinspection SlowArrayOperationsInLoopInspection */
    
              $yaml = array_merge_recursive($yaml, $extraOptions);
    
              $rendered = Yaml::dump($yaml, 9, 2);
    
    
              // Render the string again so that custom content can also use variables
    
              $rendered = $this->config->render($filename, $rendered);
    
            elseif ($extraOptions !== NULL) {
              $rendered .= $extraOptions;
            }
    
            if ($fs->exists($file)) {
    
              if (md5_file($file) === md5($rendered)) {
    
                continue;
              }
              $orig_file = $file . '.orig';
              if ($fs->exists($orig_file)) {
                $fs->remove($orig_file);
              }
              $fs->rename($file, $orig_file);
              if (!$orig_ignored) {
    
                $this->gitIgnore('*.orig');
    
                $orig_ignored = TRUE;
              }
            }
    
            if (empty($def['add2git'])) {
    
              $this->gitIgnore($filename);
    
    jurgenhaas's avatar
    jurgenhaas committed
            file_put_contents($file, $rendered);
          }
    
          if (isset($def['link']) && ($def['link'] !== $settingsPath)) {
    
            $link = $def['link'] . '/' . $filename;
    
    jurgenhaas's avatar
    jurgenhaas committed
            if (!$fs->exists($link)) {
              $rel = substr($fs->makePathRelative($file, $projectRoot . '/' . $link), 3, -1);
              $fs->symlink($rel, $link);
            }
          }
    
          $fs->chmod($file, $def['mode'] ?? 0664);
    
    jurgenhaas's avatar
    jurgenhaas committed
        }
    
    
    jurgenhaas's avatar
    jurgenhaas committed
        // Make sure that settings.docker.php gets called from settings.php.
    
        $settingsPhpFile = $settingsPath . '/settings.php';
    
        if ($fs->exists($settingsPhpFile)) {
    
          $settingsPhp = file_get_contents($settingsPhpFile);
          if (strpos($settingsPhp, 'settings.docker.php') === FALSE) {
    
            $settingsPhp .= "\n\nif (file_exists(__DIR__ . '/settings.docker.php')) {\n  include __DIR__ . '/settings.docker.php';\n}\n";
    
            file_put_contents($settingsPhpFile, $settingsPhp);
          }
    
        // Setup BackstopJS.
    
        $this->gitIgnore('tests/backstop/backstop.json');
        $this->gitIgnore('tests/backstop/backstop-script-*');
    
        $this->gitIgnore('tests/backstop/backstop_data/bitmaps_test');
        $this->gitIgnore('tests/backstop/backstop_data/html_report');
    
        $this->gitLFS('tests/backstop/**/*.png');
    
        if (getenv('LAKEDROPS_BUILD_NG') !== 'yes') {
          $this->updateTraefik();
        }
    
    
        // Set permissions, see https://wodby.com/stacks/drupal/docs/local/permissions
        exec('setfacl -dR -m u:$(whoami):rwX -m u:82:rwX -m u:100:rX -m g::rwX ' . $projectRoot . ' >/dev/null 2>&1');
        exec('setfacl -R -m u:$(whoami):rwX -m u:82:rwX -m u:100:rX -m g::rwX ' . $projectRoot . ' >/dev/null 2>&1');
      }
    
      /**
       * Configure Traefik on the host for all projects.
       */
    
      public function configureTraefik(): void {
    
        if (!$this->isDevMode()) {
          return;
        }
        $this->init();
    
      private function updateTraefik(): void {
    
        $traefik = new Traefik(
          $this->config->readValue('projectname'),
          $this->config->readValue(['traefik', 'domain']),
          $this->config->readValue(['traefik', 'port']),
          $this->config->readValue(['traefik', 'ports']),
          $this->config->readValue(['traefik', 'cert']),
          $this->config->readValue(['traefik', 'key'])
        );
    
        if ($this->config->readValue(['traefik', 'portainer'])) {
    
          $traefik->setAddonPortainer(TRUE);
        }
    
    jurgenhaas's avatar
    jurgenhaas committed
        if ($hub_token = $this->config->readValue(['traefik', 'hub_token'])) {
          $traefik->setHubToken($hub_token);
        }
    
    jurgenhaas's avatar
    jurgenhaas committed
      }
    
    
    jurgenhaas's avatar
    jurgenhaas committed
      /**
       * List of files and settings on how to handle them.
       *
       * @param string $projectRoot
       *   Name of the project's root directory.
       * @param string $webRoot
       *   Name of the web's root directory.
       * @param string $settingsPath
       *   Name of the settings directory.
       *
       * @return array
       *   List of files.
       */
    
      protected function getFiles(string $projectRoot, string $webRoot, string $settingsPath): array {
    
    jurgenhaas's avatar
    jurgenhaas committed
        $files = [
    
    jurgenhaas's avatar
    jurgenhaas committed
          'settings.docker.php' => [
    
            'dest' => $projectRoot . '/' . $settingsPath,
    
    jurgenhaas's avatar
    jurgenhaas committed
            'link' => $webRoot . '/sites/default',
          ],
    
    jurgenhaas's avatar
    jurgenhaas committed
          'docker-compose.yml' => [
    
    jurgenhaas's avatar
    jurgenhaas committed
            'dest' => $projectRoot,
    
    jurgenhaas's avatar
    jurgenhaas committed
          ],
    
          'drushrc.php' => [
            'dest' => $projectRoot . '/drush',
          ],
    
          'default.site.yml' => [
            'dest' => $projectRoot . '/drush/sites',
    
    jurgenhaas's avatar
    jurgenhaas committed
          'stage.site.yml' => [
            'dest' => $projectRoot . '/drush/sites',
            'add2yaml' => TRUE,
          ],
    
          'drush.yml' => [
            'dest' => $projectRoot . '/drush',
    
          'wkhtmltox.sh' => [
            'dest' => $projectRoot . '/.docker-init',
    
          ],
          'backstop.json' => [
            'source' => 'tests/backstop/',
            'dest' => $projectRoot . '/tests/backstop',
            'add2yaml' => TRUE,
    
          ],
          'backstop-script-before' => [
            'source' => 'tests/backstop/',
            'dest' => $projectRoot . '/tests/backstop',
            'add2yaml' => TRUE,
            'mode' => 0775,
          ],
          'backstop-script-after' => [
            'source' => 'tests/backstop/',
            'dest' => $projectRoot . '/tests/backstop',
            'add2yaml' => TRUE,
            'mode' => 0775,
    
          ],
          'vhost.conf' => [
            'dest' => $projectRoot . '/apache',
    
            'condition' => $this->config->readValue(['webserver', 'overwriteconfig']),
    
          'mhout.json' => [
            'dest' => $projectRoot . '/tests',
          ],
    
    jurgenhaas's avatar
    jurgenhaas committed
        ];
    
    jurgenhaas's avatar
    jurgenhaas committed
        if (getenv('LAKEDROPS_BUILD_NG') === 'yes') {
    
          if ($this->config->readValue(['backup', 'enable'])) {
            $files['config.yaml'] = [
              'source' => 'backup/',
              'dest' => $projectRoot . '/backup',
            ];
            $files['crontab.txt'] = [
              'source' => 'backup/',
              'dest' => $projectRoot . '/backup',
            ];
          }
    
          // Manage crontabs and optionally add them to heathcheck-io.
    
          $hj_url = getenv('HEALTHCHECK_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_timezone = $this->env->receiveGlobal('HEALTHCHECK_API_TIMEZONE', '', 'Europe/Berlin');
    
          if (!empty($hj_url) && !empty($hj_api_key)) {
            $hj_manager = new HealthchecksManager($hj_api_key, $hj_url . '/api/v1/');
    
            try {
              $hj_checks = $hj_manager->listChecks();
            }
            catch (HealthchecksFailureException | HealthchecksUnauthorisedException $e) {
              // Ignoring this for now.
            }
          }
    
          $activeTasks = [];
    
    jurgenhaas's avatar
    jurgenhaas committed
          foreach ($this->config->readValue('crontabs') ?? [] as $user => $tasks) {
    
    jurgenhaas's avatar
    jurgenhaas committed
            foreach ($tasks as $name => $task) {
    
              $disabled = !empty($task['disabled']);
    
              $command = $task['command'];
    
              $schedule = $task['schedule'];
    
              $ping_url = FALSE;
    
              $task['name'] = $name;
    
              if (isset($hj_manager)) {
                unset($task['disabled'], $task['command']);
                $task['channels'] = $hj_api_channels;
                $task['tags'] = implode(' ', ['d4d', $hj_project, $hj_branch]);
    
                $task['tz'] = $hj_timezone;
    
                if (count(explode(' ', $task['schedule'])) === 6) {
                  // Schedule contains seconds, they need to be removed.
                  $task['schedule'] = substr($task['schedule'], strpos($task['schedule'], ' ') + 1);
                }
    
    
                $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);
    
                    $parts = explode('/', $check['ping_url']);
                    $uuid = array_pop($parts);
    
                    $hj_check = new Healthchecks($uuid, $hj_url . '/ping/');
    
                    $hj_check->success();
    
                  catch (HealthchecksFailureException | HealthchecksUuidNotFoundException | 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) {
    
                  $ping_url = $check['ping_url'];
    
                if (count(explode(' ', $schedule)) === 5) {
    
                  // Add leading "0" for seconds.
    
                  $schedule = '0 ' . $schedule;
    
                if ($user === 'borgmatic') {
                  $backup = $this->config->readValue('backup');
                  $backup['healthckeck_url'][$command] = $ping_url;
    
                  $this->config->setValue('backup', $backup, FALSE);
    
                }
                else {
                  $activeTasks[] = [
                    'name' => $task['name'],
                    'schedule' => $schedule,
                    'command' => $command,
                    'ping_url' => $ping_url,
                    'user' => $user,
                  ];
                }
    
            if (!empty($activeTasks)) {
              $files['jobs.ini'] = [
    
                'source' => 'crontabs/template.twig',
                'dest' => $projectRoot . '/crontabs',
    
    jurgenhaas's avatar
    jurgenhaas committed
          }
        }
        return $files;
    
    jurgenhaas's avatar
    jurgenhaas committed
      }
    
    
    jurgenhaas's avatar
    jurgenhaas committed
      /**
       * Determine local ipv4 address.
       *
       * @param string|null $interface
       *   The name of the interface for which to determine the ipv4 address.
       *
       * @return string|array
       *   The ipv4 address(es).
       */
    
      private function getLocalIpv4(string $interface = NULL) {
    
        $out = explode(PHP_EOL, shell_exec('LC_ALL=C /sbin/ifconfig'));
        $local_addrs = array();
        $ifname = 'unknown';
        foreach ($out as $str) {
          $matches = array();
          if (preg_match('/^([a-z0-9]+)(:\d{1,2})?(\s)+Link/', $str, $matches)) {
            $ifname = $matches[1];
    
            if ($matches[2] !== '') {
    
              $ifname .= $matches[2];
            }
    
    jurgenhaas's avatar
    jurgenhaas committed
          }
          elseif (preg_match('/inet addr:((?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:[.](?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3})\s/', $str, $matches)) {
    
            $local_addrs[$ifname] = $matches[1];
          }
        }
    
        if (!isset($interface)) {
          return $local_addrs;
        }
    
        return $local_addrs[$interface] ?? '127.0.0.1';
    
      /**
       * @return string
       */
      private function getDockerGateway(): string {
        $container = $this->readContainerConfig();
        return $container['NetworkSettings']['Gateway'];
      }
    
    
      /**
       * @return string
       */
    
      private function getDockerProxy(): string {
    
    jurgenhaas's avatar
    jurgenhaas committed
        foreach ($this->readNetworkConfig()['Containers'] as $container) {
    
          if (isset($container['Name']) && in_array($container['Name'], ['traefik', 'traefik_traefik_1', 'traefik-traefik-1'])) {
    
            return explode('/', $container['IPv4Address'])[0];
          }
        }
        return '127.0.0.1';
      }
    
    
      /**
       * @param $projectRoot
       *
       * @return string
       */
    
      private function getDockerMountSource($projectRoot): string {
    
        $currentDir = getcwd();
        $container = $this->readContainerConfig();
        foreach ($container['Mounts'] as $mount) {
          if (empty($projectRoot)) {
            if ($currentDir === $mount['Destination']) {
              return $mount['Source'];
            }
          }
    
    jurgenhaas's avatar
    jurgenhaas committed
          else if (strpos($projectRoot, $mount['Destination']) === 0) {
            return $mount['Source'] . substr($projectRoot, strlen($mount['Destination']));
    
      /**
       * @return array
       */
      private function readContainerConfig(): array {
    
    jurgenhaas's avatar
    jurgenhaas committed
          $output = [];
          exec('basename "$(cat /proc/1/cpuset)"', $output);
          $id = reset($output);
          if ($id === '/') {
            $id = getenv('COMPOSE_PROJECT_NAME') . '_l3d';
          }
    
          $output = [];
          exec('docker container inspect ' . $id, $output);
    
          return json_decode(implode('', $output), TRUE)[0];
    
        return [
          'NetworkSettings' => [
            'Gateway' => '127.0.0.1',
          ],
          'Mounts' => [],
        ];
    
    jurgenhaas's avatar
    jurgenhaas committed
      private function readNetworkConfig(): array {
    
        try {
          $output = [];
    
    jurgenhaas's avatar
    jurgenhaas committed
          exec('docker network inspect traefik-public', $output);
    
          return json_decode(implode('', $output), TRUE)[0];
        }
    
          // Ignore.
        }
        return [
          'Containers' => [],
        ];
      }
    
    
      /**
       * @return array
       */
      private function backstopDefaults(): array {
        return [
    
    jurgenhaas's avatar
    jurgenhaas committed
          'id' => 'drupal',
    
          'd4dscripts' => [
            'before' => [],
            'after' => [],
          ],
          'viewports' => [
            'desktop' => [
              'width' => 1960,
              'height' => 1280,
            ],
          ],
          'scenarios' => [
    
    jurgenhaas's avatar
    jurgenhaas committed
    }