Skip to main content

Verifying your Drupal site's configuration against changes from dependency updates

Published on

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"