<?php /** * @file * Contains \LakeDrops\Docker4Drupal\Handler. */ namespace LakeDrops\Docker4Drupal; use Composer\Package\PackageInterface; use Composer\Composer; use Composer\IO\IOInterface; use Composer\Script\Event as ScriptEvent; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Yaml\Yaml; class Handler { /** * @var \Composer\Composer */ protected $composer; /** * @var \Composer\IO\IOInterface */ protected $io; /** * @var PackageInterface */ protected $drupalCorePackage; /** * Handler constructor. * * @param Composer $composer * @param IOInterface $io */ public function __construct(Composer $composer, IOInterface $io) { $this->composer = $composer; $this->io = $io; } /** * Look up the Drupal core package object, or return it from where we cached * it in the $drupalCorePackage field. * * @return PackageInterface */ 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. * * @return PackageInterface */ protected function getPackage($name) { return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*'); } /** * Configure Drupal Project for Docker. * * @param ScriptEvent $event */ public function configureProject($event) { // We only do the fancy stuff for developers if (!$event->isDevMode()) { return; } $options = $this->getOptions(); $fs = new Filesystem(); $installationManager = $this->composer->getInstallationManager(); $drupalCorePackage = $this->getDrupalCorePackage(); if (!$drupalCorePackage) { // We are called too early, Drupal core is not available yet. return; } $corePath = $installationManager->getInstallPath($drupalCorePackage); // Directory where the root project is being created $projectRoot = getcwd(); // Directory where Drupal's index.php is located $webRoot = dirname($corePath); // Directory where this plugin is being installed $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'; } // Provide all the requiired files $twig_loader = new \Twig_Loader_Array([]); $twig = new \Twig_Environment($twig_loader); $options['webRoot'] = $webRoot . '/'; foreach ($this->getFiles($projectRoot, $webRoot, $settingsPath) as $template => $def) { $file = $def['dest'] . '/' . $template; if (!$fs->exists($file)) { $twig_loader->setTemplate($template, file_get_contents($pluginRoot . '/templates/' . $template . '.twig')); $rendered = $twig->render($template, $options); file_put_contents($file, $rendered); } if (isset($def['link']) && ($def['link'] != $settingsPath)) { $link = $def['link'] . '/' . $template; if (!$fs->exists($link)) { $rel = substr($fs->makePathRelative($file, $projectRoot . '/' . $link), 3, -1); $fs->symlink($rel, $link); } } $fs->chmod($file, 0664); } // 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); } } // Initialize local git working copy try { foreach ($this->getFiles($projectRoot, $webRoot, $settingsPath) as $template => $def) { $this->git('ignore ' . $template); } } catch (\Exception $ex) { // We're ignoring this for now } // Update host wider traefik container $traefikPath = $_SERVER['HOME'] . '/.traefik'; $traefikFile = $traefikPath . '/docker-compose.yml'; if ($fs->exists($traefikFile)) { $traefik = Yaml::parse(file_get_contents($traefikFile)); } else { $fs->mkdir($traefikPath); $traefik = [ 'version' => '2', 'services' => [ 'traefik' => [ 'image' => 'traefik', 'restart' => 'unless-stopped', 'command' => '-c /dev/null --web --docker --logLevel=DEBUG', 'networks' => [], 'ports' => [ '8000:80', '8080:8080', ], 'volumes' => [ '/var/run/docker.sock:/var/run/docker.sock', ], ], ], 'networks' => [], ]; } if (!in_array($options['projectname'], $traefik['services']['traefik']['networks'])) { $traefik['services']['traefik']['networks'][] = $options['projectname']; $traefik['networks'][$options['projectname']] = [ 'external' => [ 'name' => $options['projectname'] . '_default', ], ]; file_put_contents($traefikFile, Yaml::dump($traefik, 9, 2)); exec('docker network create ' . $options['projectname'] . '_default'); exec('docker-compose -f ' . $traefikFile . ' --project-name traefik stop'); exec('docker-compose -f ' . $traefikFile . ' --project-name traefik up -d'); } } protected function getFiles($projectRoot, $webRoot, $settingsPath) { return [ 'settings.docker.php' => [ 'dest' => $projectRoot . '/' . $settingsPath, 'link' => $webRoot . '/sites/default', ], 'docker-compose.yml' => [ 'dest' => $projectRoot, ], 'aliases.drushrc.php' => [ 'dest' => $projectRoot . '/drush', ], 'drushrc.php' => [ 'dest' => $projectRoot . '/drush', ], '.env' => [ 'dest' => $projectRoot, ], ]; } /** * Retrieve excludes from optional "extra" configuration. * * @return array */ protected function getOptions() { $extra = $this->composer->getPackage()->getExtra() + ['docker4drupal' => []]; $options = $extra['docker4drupal'] + [ 'projectname' => str_replace([' ', '-', '_', '.'], '', basename(getcwd())), 'docker0' => [ 'ip' => $this->getLocalIpv4('docker0'), ], 'drupal' => [ 'version' => '8', ], 'php' => [ 'version' => '7.0',# 5.3|5.6|7.1 'xdebug' => 1, ], 'nginx' => [ 'version' => '1.10', ], 'varnish' => [ 'enable' => 0, ], 'solr' => [ 'enable' => 0, ], 'node' => [ 'enable' => 0, ], ]; return $options; } /** * Wrapper for git command in the root directory. * * @param $command * Git command name, arguments and/or options. * @throws \Exception */ protected function git($command) { passthru(escapeshellcmd('git -c "user.email=d8-project@lakedrops.com" ' . $command), $exit_code); if ($exit_code !== 0) { throw new \Exception('Git returned a non-zero exit code'); } } 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(strlen($matches[2])>0) { $ifname .= $matches[2]; } } 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'; } }