Skip to content
Snippets Groups Projects
Handler.php 10.6 KiB
Newer Older
jurgenhaas's avatar
jurgenhaas committed
<?php

namespace LakeDrops\Docker4Drupal;

use Composer\Composer;
use Composer\IO\IOInterface;
jurgenhaas's avatar
jurgenhaas committed
use Composer\Script\Event;
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
 */
jurgenhaas's avatar
jurgenhaas committed
class Handler {

  /**
jurgenhaas's avatar
jurgenhaas committed
   * The composer object of this session.
   *
jurgenhaas's avatar
jurgenhaas committed
   * @var \Composer\Composer
   */
  protected $composer;

  /**
jurgenhaas's avatar
jurgenhaas committed
   * The input-output object of the composer session.
   *
jurgenhaas's avatar
jurgenhaas committed
   * @var \Composer\IO\IOInterface
   */
  protected $io;

  /**
jurgenhaas's avatar
jurgenhaas committed
   * The Drupal core package.
   *
   * @var \Composer\Package\PackageInterface
jurgenhaas's avatar
jurgenhaas committed
   */
  protected $drupalCorePackage;

  /**
   * Handler constructor.
   *
jurgenhaas's avatar
jurgenhaas committed
   * @param \Composer\Composer $composer
   *   The composer object of this session.
   * @param \Composer\IO\IOInterface $io
   *   The input-output object of the composer session.
jurgenhaas's avatar
jurgenhaas committed
   */
  public function __construct(Composer $composer, IOInterface $io) {
    $this->composer = $composer;
    $this->io = $io;
  }

  /**
jurgenhaas's avatar
jurgenhaas committed
   * Look up the Drupal core package object.
jurgenhaas's avatar
jurgenhaas committed
   *
jurgenhaas's avatar
jurgenhaas committed
   * @return \Composer\Package\PackageInterface
   *   The Drupal core package.
jurgenhaas's avatar
jurgenhaas committed
   */
  protected function getDrupalCorePackage() {
    if (!isset($this->drupalCorePackage)) {
      $this->drupalCorePackage = $this->getPackage('drupal/core');
    }
    return $this->drupalCorePackage;
  }

  /**
   * Retrieve a package from the current composer process.
   *
   * @param string $name
   *   Name of the package to get from the current composer installation.
   *
jurgenhaas's avatar
jurgenhaas committed
   * @return \Composer\Package\PackageInterface
   *   The package.
jurgenhaas's avatar
jurgenhaas committed
   */
  protected function getPackage($name) {
    return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*');
  }

  /**
   * Configure Drupal Project for Docker.
   *
jurgenhaas's avatar
jurgenhaas committed
   * @param \Composer\Script\Event $event
   *   The event that triggered the call of this function.
jurgenhaas's avatar
jurgenhaas committed
   *   Whether to overwrite existing config files.
jurgenhaas's avatar
jurgenhaas committed
   */
jurgenhaas's avatar
jurgenhaas committed
  public function configureProject(Event $event, $overwrite = FALSE) {
jurgenhaas's avatar
jurgenhaas committed

jurgenhaas's avatar
jurgenhaas committed
    // We only do the fancy stuff for developers.
jurgenhaas's avatar
jurgenhaas committed
    if (!$event->isDevMode()) {
      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])) {
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);
    exec('setfacl -R -m u:$(whoami):rwX -m u:82:rwX -m u:100:rX' . $projectRoot);
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) {
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',
jurgenhaas's avatar
jurgenhaas committed
    ];
  }

  /**
jurgenhaas's avatar
jurgenhaas committed
   * Retrieve options from composer.json "extra" configuration.
jurgenhaas's avatar
jurgenhaas committed
   *
   * @return array
jurgenhaas's avatar
jurgenhaas committed
   *   The options.
jurgenhaas's avatar
jurgenhaas committed
   */
  protected function getOptions() {
    $projectname = str_replace([' ', '-', '_', '.'], '', basename(getcwd()));
    $env = new Dotenv('docker4drupal', $this->io);
    $env->put('COMPOSE_PROJECT_NAME', $projectname);
jurgenhaas's avatar
jurgenhaas committed
    $extra = $this->composer->getPackage()->getExtra() + ['docker4drupal' => []];
    $options = NestedArray::mergeDeep([
      'projectname' => $projectname,
      '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')),
      ],
jurgenhaas's avatar
jurgenhaas committed
            'structure' => [
              'cache',
              'cache_*',
              'history',
              'search_*',
              'sessions',
              'watchdog',
            ],
      'drupal' => [
        'version' => '8',
      ],
      'php' => [
        'version' => '7.0',
        'xdebug' => 1,
      ],
      '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,
      ],
    ], $extra['docker4drupal']);
jurgenhaas's avatar
jurgenhaas committed
    return $options;
  }

  /**
   * Wrapper for git command in the root directory.
   *
jurgenhaas's avatar
jurgenhaas committed
   * @param string $command
jurgenhaas's avatar
jurgenhaas committed
   *   Git command name, arguments and/or options.
   */
  protected function git($command) {
    passthru(escapeshellcmd('git -c "user.email=d8-project@lakedrops.com" ' . $command));
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];
jurgenhaas's avatar
jurgenhaas committed
        if (strlen($matches[2]) > 0) {
          $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 isset($local_addrs[$interface]) ? $local_addrs[$interface] : '127.0.0.1';
  }

jurgenhaas's avatar
jurgenhaas committed
}