A Composer trick to build code simultaneously for Acquia Site Factory and Pantheon

A Composer trick to build code simultaneously for Acquia Site Factory and Pantheon

I knew that switching platforms from Acquia Site Factory to Pantheon would take a while to complete. The tasks of transferring code, files and databases could be largely automated; coordination with change managers, site owners and DNS administrators would require attention and time on the calendar. I designed a process to deploy code changes to both platforms simultaneously.

I had already concluded that we would use a Pantheon custom upstream, and created a new repository for that. I realized that the Pantheon custom upstream structure—namely managing Composer dependencies in upstream-configuration/composer.json rather than in the project root—could also work for Acquia. I moved the Drupal root from docroot/ to web/ in the codebase and Composer, and transferred all of the Drupal modules, themes, etc., into the upstream Composer file.

Acquia expects code in docroot/. It was easy enough to move the web/ folder to that location during the build process. But there was a new problem—everything in the vendor/ folder now was pointing at the wrong place, so I did not get a happy Drupal site. I didn’t want to do a massive search and replace within thousands of files within vendor, so I wanted to change the destination of the installation during the Composer build process.

It took some wrangling, but I finally figured out to switch the destination folders using composer config commands.

Simplified illustration of the build processes
Image
A simplified illustration of the processes described in the article text.

Building the codebase for Acquia

Following is an excerpt from acquia-pipelines.yml, showing the basic steps involved for building the Acquia codebase (not using BLT, which probably changes this). First I move the web-root folder. Then I set the various Composer pieces (extra.drupal-scaffold.locations and extra.installer-paths) to the web-root location Acquia anticipates. From there I do my usual Composer install, and continue on with other build tasks specific to this installation. This results in a codebase that runs on Acquia Site Factory.

Excerpt from acquia-pipelines.yml
events:
  build:
    steps:
      - build:
          type: script
          script:
            - "echo 'Moving web/ to docroot/ as Drupal root expected by Acquia'"
            - mv web/ docroot/
            - "echo 'Setting drupal-scaffold to use docroot/ as web-root location'"
            - "composer config --json extra.drupal-scaffold.locations '{ \"web-root\": \"docroot/\" }'"
            - "echo 'Setting installer-paths to use docroot/ as Drupal root'"
            - "composer config --json extra.installer-paths '{ \"docroot/core\": [ \"type:drupal-core\" ], \"docroot/libraries/{$name}\": [ \"type:drupal-library\", \"type:bower-asset\", \"type:npm-asset\" ], \"docroot/modules/contrib/{$name}\": [ \"type:drupal-module\" ], \"docroot/profiles/contrib/{$name}\": [ \"type:drupal-profile\" ], \"docroot/themes/contrib/{$name}\": [ \"type:drupal-theme\" ], \"drush/Commands/contrib/{$name}\": [ \"type:drupal-drush\" ], \"docroot/modules/custom/{$name}\": [ \"type:drupal-custom-module\" ], \"docroot/themes/custom/{$name}\": [ \"type:drupal-custom-theme\" ] }'"
            - "echo 'Composer install'"
            - composer install --prefer-dist --no-dev
Excerpt from .gitignore
# Acquia variants of rules
/docroot/core
/docroot/profiles/contrib
/docroot/modules/contrib
/docroot/themes/contrib
/docroot/libraries
/docroot/sites/*/files

Building the codebase for Pantheon

I left the Acquia Pipelines jobs in place for Acquia, and wrote a new Bitbucket Pipeline job to construct a build artifact for Pantheon. This was slightly complicated by the fact that Acquia required that we commit all of the dependencies installed by Composer—the vendor/ folder and all of the contrib modules and themes. Pantheon has an integrated Composer build process that handles these as part of their DevOps workflow, so I want to make sure that I’m not committing them.

Below is an excerpt from my bitbucket-pipelines.yml. First I clone the Pantheon upstream into a temporary build directory. I then swap out its git-related files with those from the source repo. I have now transformed the source repo into the Pantheon upstream, and commit the changes to the upstream repo.

Excerpt from bitbucket-pipelines.yml
- step: &stepPantheon
    name: Commit to Pantheon Upstream
    image: composer:2.2.3
    deployment: staging
    caches:
      - composer
    script:
      - TMP_BUILD_DIR="tmp-build-pantheon"
      - git clone **upstream-repo-credentials** ${TMP_BUILD_DIR}
      - mv .git .git_source
      - mv .gitignore .gitignore_source
      - mv ${TMP_BUILD_DIR}/.git ./.git
      - mv .gitignore_pantheon .gitignore
      - git add -A
      - git status   # This can be helpful for troubleshooting in the UI
      - git config user.name "Bitbucket Pipelines"
      - git config user.email "**your-email-address**"
      - git commit -m "Bitbucket Pipelines build for ${BITBUCKET_COMMIT::7}"
      - git push
      - echo 'Unicorns and rainbows!'
Excerpt from .gitignore_pantheon
# Pantheon variants of rules
/web/core
/web/profiles/contrib
/web/modules/contrib
/web/themes/contrib
/web/libraries
/web/sites/*/files

# Other files we don't need on Pantheon
.acquia-cli.yml
acquia-pipelines.yml
/factory-hooks/

Deployment

Our Acquia Pipeline job was already set to deploy automatically into our dev environment on Site Factory. I then ran a shell script that applied the upstream updates to all of our Pantheon sites (I’d later automate this as a GitHub Action).

Once we had all of our sites migrated over to Pantheon, I removed the Acquia Pipeline job and platform-specific files that were no longer needed. It would, however, be feasible to leave both processes in place if there was an ongoing need to support both platforms or others.