diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..12bcb27e4ee7bbca7f74423a87d205926d2ad1d2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# Drupal editor configuration normalization +# @see http://editorconfig.org/ + +# This is the top-most .editorconfig file; do not search in parent directories. +root = true + +# All files. +[*] +end_of_line = LF +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[{composer.json,composer.lock}] +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7579f74311d35aae05dd0f0a54537ea7a0034e89 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor +composer.lock diff --git a/composer.json b/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..80b92c34c25dac834aa3bc92f49e1e6a93d9da8c --- /dev/null +++ b/composer.json @@ -0,0 +1,43 @@ +{ + "name": "lakedrops/docker4drupal", + "description": "Composer Plugin to prepare local Drupal development environment for Docker.", + "type": "composer-plugin", + "keywords": ["Drupal", "Development", "Docker"], + "homepage": "https://gitlab.lakedrops.com/lakedrops/docker4drupal", + "license": "GPL-2.0+", + "authors": [ + { + "name": "Jürgen Haas", + "email": "juergen@paragon-es.de", + "homepage": "https://www.paragon-es.de", + "role": "Drupal Expert" + }, + { + "name": "Richard Papp", + "email": "richard.papp@boromino.com", + "homepage": "http://www.boromino.com", + "role": "Drupal Expert" + } + ], + "support": { + "issues": "https://gitlab.lakedrops.com/lakedrops/docker4drupal/issues", + "source": "https://gitlab.lakedrops.com/lakedrops/docker4drupal/tree/master" + }, + "require": { + "php": ">=5.4.5", + "composer-plugin-api": "^1.0.0", + "twig/twig": "^1.23.1" + }, + "autoload": { + "psr-4": { + "LakeDrops\\Docker4Drupal\\": "src/" + } + }, + "extra": { + "class": "LakeDrops\\Docker4Drupal\\Plugin" + }, + "require-dev": { + "composer/composer": "dev-master", + "phpunit/phpunit": "^4.4.0" + } +} diff --git a/src/Handler.php b/src/Handler.php new file mode 100644 index 0000000000000000000000000000000000000000..e17013b3cc1d1e0a0c2d605c2bbfd5a18b740748 --- /dev/null +++ b/src/Handler.php @@ -0,0 +1,194 @@ +<?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 Rych\Random\Random; +use Rych\Random\Encoder\Base64Encoder; + +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 + * @param bool $force + */ + public function configureProject($event, $force = FALSE) { + + // We only do the fancy stuff for developers + if (!$event->isDevMode()) { + return; + } + + // 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. + if ($this->getPackage('lakedrops/d8-project-scaffold') && !$force) { + return; + } + + $options = $this->getOptions(); + $fs = new Filesystem(); + $installationManager = $this->composer->getInstallationManager(); + + $drupalCorePackage = $this->getDrupalCorePackage(); + $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')); + + // Link Drupal site's settings files + $twig_loader = new \Twig_Loader_Array([]); + $twig = new \Twig_Environment($twig_loader); + foreach ($this->getFiles($projectRoot, $webRoot) 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'])) { + $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); + } + + // Initialize local git working copy + try { + $this->git('ignore docker-compose.yml'); + $this->git('ignore settings.docker.php'); + } + catch (\Exception $ex) { + // We're ignoring this for now + } + + } + + protected function getFiles($projectRoot, $webRoot) { + return [ + 'settings.docker.php' => [ + 'dest' => $projectRoot . '/settings/default', + 'link' => $webRoot . '/sites/default', + ], + 'docker-compose.yml.twig' => [ + 'dest' => $projectRoot, + ], + ]; + } + + /** + * Retrieve excludes from optional "extra" configuration. + * + * @return array + */ + protected function getOptions() { + $extra = $this->composer->getPackage()->getExtra() + ['docker4drupal' => []]; + $options = $extra['docker4drupal'] + [ + 'port' => 8000, + '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'); + } + } + +} diff --git a/src/Plugin.php b/src/Plugin.php new file mode 100644 index 0000000000000000000000000000000000000000..858eb53506b6999da1fcf26fe32ccd98ea2182d6 --- /dev/null +++ b/src/Plugin.php @@ -0,0 +1,71 @@ +<?php +/** + * @file + * Contains LakeDrops\Docker4Drupal\Plugin. + */ + +namespace LakeDrops\Docker4Drupal; + +use Composer\Composer; +use Composer\EventDispatcher\EventSubscriberInterface; +use Composer\IO\IOInterface; +use Composer\Plugin\PluginInterface; +use Composer\Script\ScriptEvents; + +/** + * Composer plugin for handling docker4drupal setup. + */ +class Plugin implements PluginInterface, EventSubscriberInterface { + + /** + * @var \LakeDrops\Docker4Drupal\Handler + */ + protected $handler; + + /** + * {@inheritdoc} + */ + public function activate(Composer $composer, IOInterface $io) { + $this->handler = new Handler($composer, $io); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return array( + ScriptEvents::POST_CREATE_PROJECT_CMD => 'configureProject', + ScriptEvents::POST_INSTALL_CMD => 'configureProjectForce', + ScriptEvents::POST_UPDATE_CMD => 'configureProjectForce', + ); + } + + /** + * Configure project event callback. + * + * @param \Composer\Script\Event $event + */ + public function configureProject($event) { + $this->handler->configureProject($event); + } + + /** + * Configure project event callback. + * + * @param \Composer\Script\Event $event + */ + public function configureProjectForce($event) { + $this->handler->configureProject($event, TRUE); + } + + /** + * Script callback for putting in composer scripts to configure the project. + * + * @param \Composer\Script\Event $event + */ + public static function config($event) { + $handler = new Handler($event->getComposer(), $event->getIO()); + $handler->configureProject($event, TRUE); + } + +} diff --git a/templates/docker-compose.yml.twig b/templates/docker-compose.yml.twig new file mode 100644 index 0000000000000000000000000000000000000000..28249abe354f5fe7554f6c5a652ddc891c499f22 --- /dev/null +++ b/templates/docker-compose.yml.twig @@ -0,0 +1,112 @@ +version: "2" + +services: + mariadb: + image: wodby/mariadb:10.1-2.0.0 + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: drupal + MYSQL_USER: drupal + MYSQL_PASSWORD: drupal + ports: + - '8306:3306' + + php: + image: wodby/drupal-php:{{ php.version }}-2.0.0 + environment: + PHP_SENDMAIL_PATH: /usr/sbin/sendmail -t -i -S mailhog:1025 +{% if php.debug %} + PHP_XDEBUG: 1 + PHP_XDEBUG_DEFAULT_ENABLE: 1 +{% endif %} + volumes: + - ./:/var/www/html + + nginx: + image: wodby/drupal-nginx:{{ drupal.version }}-{{ nginx.version }}-2.0.0 + restart: unless-stopped + depends_on: + - php + environment: + NGINX_BACKEND_HOST: php + NGINX_SERVER_ROOT: /var/www/html/web/ + volumes: + - ./:/var/www/html + labels: + - 'traefik.backend=nginx' + - 'traefik.port=80' + - 'traefik.frontend.rule=Host:drupal.docker.localhost' + +{% if varnish.enable %} + varnish: + image: wodby/drupal-varnish:4.1-2.0.0 + depends_on: + - nginx + environment: + VARNISH_SECRET: secret + VARNISH_BACKEND_HOST: nginx + VARNISH_BACKEND_PORT: 80 + labels: + - 'traefik.backend=varnish' + - 'traefik.port=6081' + - 'traefik.frontend.rule=Host:varnish.drupal.docker.localhost' +{% endif %} + + redis: + image: wodby/redis:3.2-2.0.1 + + pma: + image: phpmyadmin/phpmyadmin + environment: + PMA_HOST: mariadb + PMA_USER: drupal + PMA_PASSWORD: drupal + PHP_UPLOAD_MAX_FILESIZE: 1G + PHP_MAX_INPUT_VARS: 1G + labels: + - 'traefik.backend=pma' + - 'traefik.port=80' + - 'traefik.frontend.rule=Host:pma.drupal.docker.localhost' + +{% if solr.enable %} + solr: + image: wodby/drupal-solr:8-6.4-2.0.0 + environment: + SOLR_HEAP: 1024m + labels: + - 'traefik.backend=solr' + - 'traefik.port=8983' + - 'traefik.frontend.rule=Host:solr.drupal.docker.localhost' +{% endif %} + + mailhog: + image: mailhog/mailhog + labels: + - 'traefik.backend=mailhog' + - 'traefik.port=8025' + - 'traefik.frontend.rule=Host:mailhog.drupal.docker.localhost' + +{% if node.enable %} + node: + image: node:7-alpine + working_dir: /app + labels: + - 'traefik.backend=node' + - 'traefik.port=3000' + - 'traefik.frontend.rule=Host:front.drupal.docker.localhost' + expose: + - "3000" + volumes: + - ./path/to/your/single-page-app:/app + command: sh -c 'npm install && npm run start' +{% endif %} + + traefik: + image: traefik + restart: unless-stopped + command: -c /dev/null --web --docker --logLevel=INFO + ports: + - '{{ port }}:80' + - '8080:8080' + volumes: + - /var/run/docker.sock:/var/run/docker.sock diff --git a/templates/settings.docker.php.twig b/templates/settings.docker.php.twig new file mode 100644 index 0000000000000000000000000000000000000000..7445786f0d54904fc76e6267963218ae9d1e0db3 --- /dev/null +++ b/templates/settings.docker.php.twig @@ -0,0 +1,24 @@ +<?php + +$settings['trusted_host_patterns'] = array( + '^drupal\.docker\.localhost$', +); +$databases = array ( + 'default' => array ( + 'default' => array ( + 'driver' => 'mysql', + 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql', + 'database' => 'drupal', + 'username' => 'drupal', + 'password' => 'drupal', + 'host' => 'mariadb', + 'port' => '3306', + 'prefix' => '', + ), + ), +); + +if (PHP_SAPI === 'cli') { + $databases['default']['default']['host'] = '127.0.0.1'; + $databases['default']['default']['port'] = '8306'; +} diff --git a/tests/PluginTest.php b/tests/PluginTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bae2fdea8c764ae09ea9f671fe46870cf32dd404 --- /dev/null +++ b/tests/PluginTest.php @@ -0,0 +1,24 @@ +<?php + +/** + * @file + * Contains \LakeDrops\Docker4Drupal\Tests\PluginTest. + */ + +namespace LakeDrops\Docker4Drupal\Tests; + +/** + * Tests composer plugin functionality. + */ +class PluginTest extends \PHPUnit_Framework_TestCase { + + static $name = 'docker4drupal'; + + /** + * SetUp test + */ + public function setUp() { + $this->rootDir = realpath(realpath(__DIR__ . '/..')); + } + +}