Skip to content

Conversation

@rabbitlair
Copy link
Contributor

Closes #263

This PR refactors the Tugboat integration to use DDEV configuration as the single source of truth for all service definitions. The new implementation generates a complete config.yml using Twig templates instead of separate bash scripts.

Architecture

  • New dedicated plugin: TugboatConfigPlugin handles all Tugboat logic (previously in ScaffoldInstallerPlugin)
  • Template-based generation: Uses Twig template to generate config.yml file with sub-templates for each build phase in main PHP service
  • DDEV-driven configuration: All service versions and types extracted from .ddev/config.yaml and docker-compose add-on files

Build Phase Templates

  • php-init.yml.twig - System packages, PHP extensions, Node.js installation, composer install
  • php-update.yml.twig - Composer install, sync task, file permissions
  • php-build.yml.twig - Build and update tasks
  • config.yml.twig - Main configuration template that includes phase templates

Taskfile Integration

  • PHP service: Supports sync:tugboat, update:tugboat, build:tugboat, online:tugboat tasks
  • All services: Supports tugboat:{service}:init tasks (e.g., tugboat:redis:init)
  • Custom init script: custom-init-command.sh wrapper for non-PHP service init tasks
  • Task installation: Automatically installs Taskfile in init phase using version from .taskfile file

Template Customization

  • Default templates: Located in vendor/lullabot/drainpipe/scaffold/tugboat/templates/
  • Project overrides: Place custom templates in .tugboat/drainpipe-templates/
  • Template precedence: Project templates take priority over defaults
  • Custom Twig filters: Added indent filter for proper YAML formatting

Files Structure

scaffold/tugboat/
├── templates/
│   ├── config.yml.twig           # Main Tugboat config
│   ├── php-init.yml.twig         # Init phase commands
│   ├── php-update.yml.twig       # Update phase commands
│   ├── php-build.yml.twig        # Build phase commands
│   └── settings.tugboat.php.twig # Drupal settings
└── scripts/
    ├── custom-init-command.sh    # Init wrapper for non-PHP services
    └── install-mysql-client.sh   # MySQL client installer

.tugboat/ (generated)
├── config.yml                    # Generated config (overwritten on each run)
├── config.drainpipe-override.yml # User overrides (preserved)
├── drainpipe-templates/          # User template overrides (preserved)
└── scripts/                      # Copied scripts

Please, note this is a first working version. I would like to know your thoughts about the new approach - any feedback will be more than welcome. As far as I can tell, this refactor does not break backwards compatibility while adding the custom templates feature, which I think will make easier for users to customize their Tugboat integration. Also, take into account tests are broken - once we all are happy with the approach and implementation, I will fix those tests failing.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @rabbitlair, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the Tugboat integration within the project. The primary goal is to centralize configuration management by using DDEV settings as the authoritative source for all service definitions. This is achieved through a new dedicated Composer plugin that dynamically generates the config.yml using a flexible Twig templating system, replacing previous bash scripts. The change aims to provide greater customizability and maintainability for Tugboat environments.

Highlights

  • Dedicated Tugboat Plugin: Introduced TugboatConfigPlugin to centralize all Tugboat-related logic, moving it out of ScaffoldInstallerPlugin.
  • Template-Driven Configuration: Switched to generating config.yml using Twig templates, with DDEV configuration as the single source of truth for service definitions.
  • Modular Build Phases: Tugboat build phases (init, update, build) are now defined in separate, modular Twig sub-templates for better organization and customization.
  • Enhanced Taskfile Integration: Improved support for custom Taskfile commands, allowing users to define tugboat:{service}:init, sync:tugboat, update:tugboat, build:tugboat, and online:tugboat tasks.
  • Custom Template Overrides: Users can now override default Tugboat templates by placing custom Twig files in .tugboat/drainpipe-templates/.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is a great refactoring of the Tugboat integration. Moving the logic to a dedicated plugin and adopting a template-based approach significantly improves maintainability and extensibility. The new TugboatConfigPlugin is well-structured. However, I've identified several critical regressions where conditional logic from the old shell scripts was lost in the new Twig templates, potentially breaking functionality for some project configurations (e.g., non-MariaDB databases, Memcached, Apache, Pantheon). I've also included some suggestions to improve code clarity and fix a bug in the new plugin. After addressing these points, this will be a very solid contribution.

Comment on lines 1 to 66
- |
# Install mysql or mariadb client.
apt-get update
apt-get install -y mariadb-client
- |
# Link the document root to the expected path. Tugboat uses /docroot
# by default. So, if Drupal is located at any other path in your git
# repository, change that here. This example links /web to the docroot
ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}"
- |
# Install the PHP opcache as it's not included by default and needed for
# decent performance.
docker-php-ext-install opcache
- |
# GD dependencies.
apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev
- |
# WebP dependencies.
apt-get install -y libwebp-dev libwebp7 webp libmagickwand-dev
- |
# Build and install gd.
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp
docker-php-ext-install gd
- |
# Install ImageMagick. This is recommended by both Acquia and Pantheon instead
# of GD. Lullabot will likely be publishing an ADR recommending it too.
apt-get install -y imagemagick
- |
# Install the PHP redis extension.
yes '' | pecl install -f redis
echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini
- |
# Create the Drupal private and public files directories if they aren't
# already present.
mkdir -p "${DOCROOT}/sites/default/files"
chmod 777 "${DOCROOT}/sites/default/files"
chgrp -R www-data "${DOCROOT}/sites/default/files"
- composer install
- |
# Install node
apt-get install -y ca-certificates gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=$(cat ${TUGBOAT_ROOT}/.nvmrc)
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
- |
# Ensure we use nodesource packages, even if Debian ships newer major versions.
echo 'Package: *' > /etc/apt/preferences.d/nodesource-nodejs
echo 'Pin: origin deb.nodesource.com' >> /etc/apt/preferences.d/nodesource-nodejs
echo 'Pin-Priority: 1001' >> /etc/apt/preferences.d/nodesource-nodejs
- apt-get update
- apt-get -qq install nodejs
- apt-get clean
- |
# This only works for node > 16, but that version is unsupported now anyway.
corepack enable
- |
# Validate we have the right nodejs version.
nodejs -v | grep -q v$NODE_MAJOR
- |
# Install task
TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile)
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin
{% if services.php.commands.init|default(false) %}
- task tugboat:php:init
{% endif %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This template has several regressions compared to the previous implementation, which can break builds for various project configurations. Specifically:

  1. Database Client: It hardcodes mariadb-client installation. This will fail for projects using PostgreSQL or MySQL, as the php container won't have the correct database client.
  2. PHP Extensions: It unconditionally installs the redis extension and completely omits the memcached extension. This should be conditional based on the detected memory_cache service.
  3. Apache Support: It's missing the a2enmod headers rewrite command for Apache webservers.
  4. Pantheon Support: It's missing the installation of the Pantheon Terminus CLI for projects with Pantheon integration enabled.

I've provided a complete replacement for the file that restores the correct conditional logic for these features.

- |
  # Install database client.
  apt-get update
{% if services.database.type == 'mysql' %}
  # mysql was replaced by mariadb in Debian, so we have to install it manually.
  # https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/#repo-qg-apt-repo-manual-setup
  # apt-key is also deprecated, so here we install the key manually.
  ./.tugboat/scripts/install-mysql-client.sh
{% else %}
  apt-get install -y {{ services.database.type }}-client
{% endif %}
- |
  # Link the document root to the expected path. Tugboat uses /docroot
  # by default. So, if Drupal is located at any other path in your git
  # repository, change that here. This example links /web to the docroot
  ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}"
- |
  # Install the PHP opcache as it's not included by default and needed for
  # decent performance.
  docker-php-ext-install opcache
- |
  # GD dependencies.
  apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev
- |
  # WebP dependencies.
  apt-get install -y libwebp-dev libwebp7 webp libmagickwand-dev
- |
  # Build and install gd.
  docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp
  docker-php-ext-install gd
- |
  # Install ImageMagick. This is recommended by both Acquia and Pantheon instead
  # of GD. Lullabot will likely be publishing an ADR recommending it too.
  apt-get install -y imagemagick
{% if services.memory_cache.type == "memcached" %}
- |
  # Install the PHP memcache extension.
  apt-get install -y zlib1g-dev
  yes '' | pecl install -f memcache
  echo 'extension=memcache.so' > /usr/local/etc/php/conf.d/memcache.ini
{% elseif services.memory_cache.type == "redis" %}
- |
  # Install the PHP redis extension.
  yes '' | pecl install -f redis
  echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini
{% endif %}
- |
  # Create the Drupal private and public files directories if they aren't
  # already present.
  mkdir -p "${DOCROOT}/sites/default/files"
  chmod 777 "${DOCROOT}/sites/default/files"
  chgrp -R www-data "${DOCROOT}/sites/default/files"
- composer install
- |
  # Install node
  apt-get install -y ca-certificates gnupg
  mkdir -p /etc/apt/keyrings
  curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
  NODE_MAJOR=$(cat ${TUGBOAT_ROOT}/.nvmrc)
  echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
- |
  # Ensure we use nodesource packages, even if Debian ships newer major versions.
  echo 'Package: *' > /etc/apt/preferences.d/nodesource-nodejs
  echo 'Pin: origin deb.nodesource.com' >> /etc/apt/preferences.d/nodesource-nodejs
  echo 'Pin-Priority: 1001' >> /etc/apt/preferences.d/nodesource-nodejs
- apt-get update
- apt-get -qq install nodejs
- apt-get clean
- |
  # This only works for node > 16, but that version is unsupported now anyway.
  corepack enable
- |
  # Validate we have the right nodejs version.
  nodejs -v | grep -q v$NODE_MAJOR
- |
  # Install task
  TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile)
  sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin
{% if services.php.type == 'apache-fpm' %}
- |
  # Apache
  a2enmod headers rewrite
{% endif %}
{% if pantheon %}
- |
  # Pantheon
  curl --fail -sSL https://github.com/pantheon-systems/terminus/releases/download/$(curl -L --fail --silent "https://api.github.com/repos/pantheon-systems/terminus/releases/latest" | perl -nle'print $& while m{"tag_name": "\K.*?(?=")"}g')/terminus.phar --output /usr/local/bin/terminus
  chmod 777 /usr/local/bin/terminus
{% endif %}
{% if services.php.commands.init|default(false) %}
- task tugboat:php:init
{% endif %}

Comment on lines 18 to 19
# Install YQ
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Installing yq from the latest release can introduce unexpected breaking changes into the build process, which can lead to CI failures. It's a best practice to pin dependencies to specific versions to ensure reproducible builds, similar to how the task binary version is handled in this script.

Suggested change
# Install YQ
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq
# Install YQ
YQ_VERSION=v4.40.5 # Pin to a specific version to ensure build stability.
wget https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq

Comment on lines +555 to +558
$vendor = $this->config->get('vendor-dir');
$scaffoldPath = $vendor . '/lullabot/drainpipe/scaffold/tugboat/templates';
$loader = new FilesystemLoader($scaffoldPath);
$twig = new Environment($loader);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The generateSettingsFile method initializes its own Twig environment, which does not respect project-level template overrides in .tugboat/drainpipe-templates/. If a user overrides settings.tugboat.php.twig, their changes will be ignored. This can be fixed by using the initTwigLoader() helper method, which correctly sets up the template paths.

        $twig = $this->initTwigLoader();

Comment on lines +411 to +417
$commands['php']['update'] = 'build:drupal';
if (isset($tasks['update'])) {
$commands['php']['update'] = 'update';
}
if (isset($tasks['update:tugboat'])) {
$commands['php']['update'] = 'update:tugboat';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current logic to determine the update command for the php service is a bit hard to follow due to setting a default and then conditionally overriding it. Using a more standard if/elseif/else structure would make the precedence clearer and improve maintainability.

        if (isset($tasks['update:tugboat'])) {
            $commands['php']['update'] = 'update:tugboat';
        }
        elseif (isset($tasks['update'])) {
            $commands['php']['update'] = 'update';
        }
        else {
            $commands['php']['update'] = 'build:drupal';
        }

return $services;
}

private function initTwigLoader(Environment &$twig = null): Environment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The method signature private function initTwigLoader(Environment &$twig = null): Environment is a bit confusing. The pass-by-reference parameter $twig is not used as such, and the function always creates and returns a new Environment instance. A simpler signature would improve code clarity.

    private function initTwigLoader(): Environment

@github-actions github-actions bot temporarily deployed to lullabotsandbox.dev December 18, 2025 21:03 Inactive
Copy link
Member

@deviantintegral deviantintegral left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nice first pass!


# Install Taskfile
TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile)
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's pin this to a version, and add a Renovate custom manager to keep it updated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Taskfile version is already pinned by the .taskfile file :)

# environment:
services:
php:
http: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh, should this be true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, setting http to false tells the tugboat container to not listen to plain HTTP traffic, andforces all traffic to go via https instead

- `php-init.yml.twig` - Init phase commands
- `php-update.yml.twig` - Update phase commands
- `php-build.yml.twig` - Build phase commands
- `config.yml.twig` - Complete Tugboat configuration (advanced)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

@github-actions github-actions bot requested a deployment to lullabotsandbox.dev January 5, 2026 16:47 In progress
@github-actions github-actions bot temporarily deployed to lullabotsandbox.dev January 5, 2026 17:04 Inactive
@github-actions github-actions bot temporarily deployed to lullabotsandbox.dev January 5, 2026 17:12 Inactive
@github-actions github-actions bot temporarily deployed to lullabotsandbox.dev January 5, 2026 17:47 Inactive
@github-actions github-actions bot temporarily deployed to lullabotsandbox.dev January 5, 2026 17:55 Inactive
@github-actions github-actions bot requested a deployment to pantheon-pr-1073 January 5, 2026 18:20 In progress
@github-actions github-actions bot temporarily deployed to lullabotsandbox.dev January 5, 2026 18:35 Inactive
@github-actions github-actions bot temporarily deployed to lullabotsandbox.dev January 5, 2026 18:48 Inactive
@rabbitlair
Copy link
Contributor Author

This one is now ready for review. Tests have been fixed, and the Drainpipe site in Tugboat is able to build properly.

Please, any feedback will be more than welcome. Thanks!

@github-actions github-actions bot temporarily deployed to lullabotsandbox.dev January 5, 2026 18:57 Inactive
@github-actions github-actions bot temporarily deployed to lullabotsandbox.dev January 5, 2026 19:04 Inactive
@deviantintegral
Copy link
Member

@rabbitlair perhaps as a first step, either fix or resolve the Gemini comments?

Do you have test steps you'd like people to go through?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Thoughts on how we extend and customize the Tugboat integrations

3 participants