Previously, I wrote about leveraging Dependabot to automate dependency updates for my Drupal projects. Automating dependency updates saves a lot of time. However, Drupal core or contributed modules may perform updates that modify the configuration state of your Drupal site that needs to be re-exported. A standard continuous integration pipeline that only performs code linting and tests will miss these changes. For my projects, I have a job that runs the update process for the Drupal site and fails if the configuration has been modified at runtime by these updates. This lets me know I have to perform a manual action locally to update the exported configuration.
I primarily use GitHub Actions nowadays, and this post will walk through the steps in that format. But it is easily translatable to any CI system. I'll provide a more generic alternative example at the end.
Defining the config_verification
job
First, we define the basics of the job.
jobs:
config_verification:
name: Verify configuration changes
runs-on: ubuntu-latest
I'm adding a MySQL service to the job so that I can install the Drupal site.
services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: ci
ports:
- 3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
Now, we can define the steps for the job. The workflow begins by checking out the main branch of our repository. This step ensures we start from a stable, up-to-date baseline before applying any new changes.
steps:
- name: Checkout master
uses: 'actions/checkout@v4
with:
ref: 'main'
Next, we set up PHP for use within the job using setup-php
.
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: none
php-version: 8.1
Using Composer, we install the original dependencies of the project. This step ensures that all the necessary components are in place as they were during the last stable build.
- name: Composer install original dependencies
run: composer install
We then prepare for the Drupal installation by installing the baseline site. This creates a controlled environment that mirrors our production setup. My projects have a simple installation profile that installs the existing configuration. You can use the --existing-config
parameter for Drush if you do not have this setup.
- name: Install baseline
run: php vendor/bin/drush si --db-url=mysql://root:@127.0.0.1:${{ job.services.mysql.ports[3306] }}/ci --yes
Then, use the checkout
action to check back to the active branch for the job. The only argument required is clean: true
. This prevents the action from destroying the existing in-place repository.
- name: Checkout PR
uses: actions/checkout@v4
with:
clean: false
With the active branch checked out again, we run Composer again to install any new dependencies introduced by the updates.
- name: Composer install new dependencies
run: composer install
Running database updates is next. This step applies the new changes and updates from the pull request to our Drupal installation.
- name: Run updates on new branch
run: php vendor/bin/drush updb -y
Finally, we check for any configuration changes. This step is the crux of our workflow, where we verify that no unintended configuration changes have occurred. If configuration changes are detected, the job will fail.
- name: Check for config changes
run: php vendor/bin/drush config:status 2>&1 | grep "No differences"
This workflow is more than a convenience; it's a safeguard. In Drupal, configuration changes can often be noticed during updates, leading to potential inconsistencies and deployment issues. Automating the verification process significantly reduces the risk of such problems, ensuring a smoother and more reliable update process.
Here is the job:
jobs:
config_verification:
name: Verify configuration changes
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: ci
ports:
- 3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- name: Checkout master
uses: 'actions/checkout@v4
with:
ref: 'main'
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: none
php-version: 8.1
- name: Composer install original dependencies
run: composer install
- name: Install baseline
run: php vendor/bin/drush si --db-url=mysql://root:@127.0.0.1:${{ job.services.mysql.ports[3306] }}/ci --yes
- name: Checkout PR
uses: actions/checkout@v4
with:
clean: false
- name: Composer install new dependencies
run: composer install
- name: Run updates on new branch
run: php vendor/bin/drush updb -y
- name: Check for config changes
run: php vendor/bin/drush config:status 2>&1 | grep "No differences"
A more generic example for other continuous integration systems
I have this version for CircleCI in the ContribKanban repository's .circleci/config.yml
.
Here's a more generic sample that could be fed as scripts per step on Jenkins, Bitbucket Pipelines, or GitLab CI.
config_verification:
# Clone the repository into a temporary director, using references from the local workspace.
- git clone --reference $WORKSPACE --dissociate $GIT_URL /tmp/sut
- |
cd /tmp/sut
git checkout main
composer install
php vendor/bin/drush site:install
# This assumes GIT_BRANCH has been provided as the PR's branch name, and GIT_COMMIT is the hash.
- |
cd /tmp/sut
git checkout -b $GIT_BRANCH $GIT_COMMIT
php vendor/bin/drush updb --yes
- cd /tmp/sut && php vendor/bin/drush config:status 2>&1 | grep "No differences"
Want more? Sign up for my weekly newsletter