Skip to content
Snippets Groups Projects
Handler.php 10.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • jurgenhaas's avatar
    jurgenhaas committed
    <?php
    
    namespace LakeDrops\Docker4Drupal;
    
    
    use LakeDrops\Component\Composer\BaseHandler;
    
    use LakeDrops\Component\Dotenv\Dotenv;
    
    use LakeDrops\DockerTraefik\Traefik;
    
    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
    
    
      /**
       * @var array
       */
      protected $options;
    
    
    jurgenhaas's avatar
    jurgenhaas committed
      /**
       * Configure Drupal Project for Docker.
       *
    
    jurgenhaas's avatar
    jurgenhaas committed
       *   Whether to overwrite existing config files.
    
       *
       * @throws \Twig_Error_Loader
       * @throws \Twig_Error_Runtime
       * @throws \Twig_Error_Syntax
    
    jurgenhaas's avatar
    jurgenhaas committed
       */
    
      public function configureProject($overwrite = FALSE) {
    
    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;
        }
    
        $options = $this->getOptions();
        $fs = new Filesystem();
        $installationManager = $this->composer->getInstallationManager();
    
    
        if (isset($options['webroot'])) {
          if (!$fs->exists($options['webroot'])) {
            return;
          }
          $webRoot = $options['webroot'];
        }
        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);
    
    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 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.
        $settingsPath = $webRoot . '/sites/default';
        if ($this->getPackage('lakedrops/d8-project-scaffold')) {
          if (!$fs->exists($projectRoot . '/settings/default')) {
            return;
          }
          $settingsPath = 'settings/default';
        }
    
    
        // Check if SSH auth sockets are supported.
        $ssh_auth_sock = getenv('SSH_AUTH_SOCK');
        $options['php']['ssh'] = !empty($ssh_auth_sock);
    
    
    jurgenhaas's avatar
    jurgenhaas committed
        // Provide all the required files.
    
    jurgenhaas's avatar
    jurgenhaas committed
        $twig_loader = new \Twig_Loader_Array([]);
        $twig = new \Twig_Environment($twig_loader);
    
    jurgenhaas's avatar
    jurgenhaas committed
        $options['webRoot'] = $webRoot . '/';
    
        $orig_ignored = FALSE;
    
        foreach ($this->getFiles($projectRoot, $webRoot, $settingsPath) as $template => $def) {
    
          if (!$fs->exists($def['dest'])) {
            $fs->mkdir($def['dest']);
          }
    
          $twig_loader->setTemplate($template, $template);
          $filename = $twig->render($template, $options);
          $file = $def['dest'] . '/' . $filename;
    
          if ($overwrite || !$fs->exists($file)) {
    
            $twig_loader->setTemplate($filename, file_get_contents($pluginRoot . '/templates/' . $template . '.twig'));
            $rendered = $twig->render($filename, $options);
            if (!empty($def['add2yaml']) && isset($options[$filename])) {
    
              /** @noinspection SlowArrayOperationsInLoopInspection */
    
    jurgenhaas's avatar
    jurgenhaas committed
              $yaml = array_merge_recursive($yaml, $options[$filename]);
    
              $rendered = Yaml::dump($yaml, 9, 2);
            }
    
            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->git('ignore *.orig');
                $orig_ignored = TRUE;
              }
            }
            else {
    
              $this->git('ignore ' . $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, 0664);
        }
    
    
    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);
          }
    
        $traefik = new Traefik($options['projectname']);
        $traefik->update();
    
    
        // 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 ' . $projectRoot . ' >/dev/null 2>&1');
        exec('setfacl -R -m u:$(whoami):rwX -m u:82:rwX -m u:100:rX ' . $projectRoot . ' >/dev/null 2>&1');
    
    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($projectRoot, $webRoot, $settingsPath): array {
    
    jurgenhaas's avatar
    jurgenhaas committed
        return [
          '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
          ],
    
          'aliases.drushrc.php' => [
            'dest' => $projectRoot . '/drush',
          ],
          'drushrc.php' => [
            'dest' => $projectRoot . '/drush',
          ],
    
          'default.site.yml' => [
            'dest' => $projectRoot . '/drush/sites',
    
          ],
          'drush.yml' => [
            'dest' => $projectRoot . '/drush',
    
          'wkhtmltox.sh' => [
            'dest' => $projectRoot . '/.docker-init',
          ]
    
    jurgenhaas's avatar
    jurgenhaas committed
        ];
      }
    
      /**
    
    jurgenhaas's avatar
    jurgenhaas committed
       * Retrieve options from composer.json "extra" configuration.
    
    jurgenhaas's avatar
    jurgenhaas committed
       *
    
       * @param null $key
       *   Optional name of an option to be received instead of the full set of options.
       *
    
    jurgenhaas's avatar
    jurgenhaas committed
       * @return array|string
    
    jurgenhaas's avatar
    jurgenhaas committed
       *   The options.
    
    jurgenhaas's avatar
    jurgenhaas committed
       */
    
    jurgenhaas's avatar
    jurgenhaas committed
      public function getOptions($key = NULL) {
    
        if ($this->options === NULL) {
          $env = new Dotenv('docker4drupal', $this->io);
          $projectname = getenv('COMPOSE_PROJECT_NAME');
          if (empty($projectname)) {$projectname = str_replace([' ', '-', '_', '.'], '', basename(getcwd()));
            $env->put('COMPOSE_PROJECT_NAME', $projectname);
          }
          $extra = $this->composer->getPackage()->getExtra() + ['docker4drupal' => []];
          $options = NestedArray::mergeDeep([
            'projectname' => $projectname,
    
            'ci_home' => '/home/gitlab-runner',
    
            'docker0' => [
              'ip' => $this->getLocalIpv4('docker0'),
            ],
            'live' => [
              'root' => '',
              'uri' => '',
              'host' => '',
              'user' => $env->receive('live_host_username', 'Remote username for host of the live site', getenv('USER')),
            ],
            'drush' => [
              'sql' => [
                'tables' => [
                  'structure' => [
                    'cache',
                    'cache_*',
                    'history',
                    'search_*',
                    'sessions',
                    'watchdog',
                  ],
                  'skip' => [
                    'migration_*',
                  ],
    
            'drupal' => [
              'version' => '8',
            ],
            'php' => [
              'version' => '7.0',
    
              'xdebug' => $this->isLocalDevMode() ? 1 : 0,
    
            ],
            'webserver' => [
              'type' => 'apache',
            ],
            'varnish' => [
              'enable' => 0,
            ],
            'redis' => [
              'version' => '4.0',
            ],
            'dbbrowser' => [
              'type' => 'pma',
            ],
            'solr' => [
              'enable' => 0,
              'version' => '6.6',
            ],
            'node' => [
              'enable' => 0,
              'key' => '',
              'path' => '',
            ],
            'memcached' => [
              'enable' => 0,
            ],
            'rsyslog' => [
              'enable' => 0,
            ],
            'athenapdf' => [
              'enable' => 0,
              'key' => '',
            ],
            'blackfire' => [
              'enable' => 0,
              'id' => '',
              'token' => '',
            ],
            'webgrind' => [
              'enable' => 0,
            ],
            'wkhtmltox' => [
              'enable' => 0,
            ],
          ], $extra['docker4drupal']);
    
          if ($this->isCiContext() || $this->isLocalDevMode()) {
    
            $projectRoot = $this->getDockerMountSource(getenv('CI_PROJECT_DIR'));
    
            $projectRoot = getcwd();
    
          }
          $options['projectroot'] = $projectRoot;
    
    
          $this->options = $env->replaceEnvironmentVariables($options);
        }
        if ($key !== NULL) {
          return $this->options[$key];
        }
        return $this->options;
    
    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($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';
    
      /**
       * @param $projectRoot
       *
       * @return string
       */
    
      private function getDockerMountSource($projectRoot): string {
    
        try {
          $output = [];
          exec('basename "$(cat /proc/1/cpuset)"', $output);
          $id = reset($output);
          $output = [];
          exec('docker container inspect ' . $id, $output);
          $container = json_decode(implode('', $output), TRUE)[0];
          foreach ($container['Mounts'] as $mount) {
            if (strpos($projectRoot, $mount['Destination']) === 0) {
              return $mount['Source'] . substr($projectRoot, strlen($mount['Destination']));
            }
          }
        }
        catch (\Exception $ex) {
          // Ignore.
        }
        return getcwd();
      }
    
    
    jurgenhaas's avatar
    jurgenhaas committed
    }