From 96978bc116cbd70016d7f4c6bf6f1b42076dd7e9 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 28 Oct 2026 08:55:04 +0100 Subject: [PATCH 1/6] feat: add config drift checks to Drupal site workflow Checks that installed config exports unchanged, that update hooks leave no unexported config, and (when drupal/config_inspector is installed) that config matches its schema. --- docs/github-actions-templates.md | 17 ++++++++- github/workflows/drupal/site.yaml | 58 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/docs/github-actions-templates.md b/docs/github-actions-templates.md index c462c63a..fd0f94fc 100644 --- a/docs/github-actions-templates.md +++ b/docs/github-actions-templates.md @@ -1,6 +1,6 @@ @@ -182,6 +182,21 @@ pull request). services. 3. The Drupal site can be installed from existing config. +#### Config checks + +1. Config is up to date: exporting config from a site installed from config + must not change anything in `config/`. +2. Config changed by update hooks is exported: after running database + updates (on pull request), the active config may only differ from the + config in code where the pull request itself changes config. +3. Config matches its schema (only if + [drupal/config_inspector](https://www.drupal.org/project/config_inspector) + is a dev requirement in `composer.json`): + + ``` shell + docker compose run --rm phpfpm composer require --dev drupal/config_inspector + ``` + --- [github/workflows/drupal/styles.yaml](github/workflows/drupal/styles.yaml) diff --git a/github/workflows/drupal/site.yaml b/github/workflows/drupal/site.yaml index c9447fc4..84d51dcd 100644 --- a/github/workflows/drupal/site.yaml +++ b/github/workflows/drupal/site.yaml @@ -15,6 +15,21 @@ ### services and the default settings match connection credentials for these ### services. ### 3. The Drupal site can be installed from existing config. +### +### #### Config checks +### +### 1. Config is up to date: exporting config from a site installed from config +### must not change anything in `config/`. +### 2. Config changed by update hooks is exported: after running database +### updates (on pull request), the active config may only differ from the +### config in code where the pull request itself changes config. +### 3. Config matches its schema (only if +### [drupal/config_inspector](https://www.drupal.org/project/config_inspector) +### is a dev requirement in `composer.json`): +### +### ``` shell +### docker compose run --rm phpfpm composer require --dev drupal/config_inspector +### ``` name: Drupal @@ -59,6 +74,23 @@ jobs: # Install the site from config docker compose exec phpfpm vendor/bin/drush site:install --existing-config --yes + - name: Check that config is up to date + run: | + # Exporting a site installed from config must reproduce the config. + docker compose exec phpfpm vendor/bin/drush config:export --yes + git status --porcelain config/ + git diff --exit-code config/ + test -z "$(git status --porcelain config/)" + + - name: Check that config matches its schema + run: | + if ! docker compose exec phpfpm composer show drupal/config_inspector > /dev/null 2>&1; then + echo "::notice::drupal/config_inspector is not installed; skipping config schema check" + exit 0 + fi + docker compose exec phpfpm vendor/bin/drush pm:install config_inspector --yes + docker compose exec phpfpm vendor/bin/drush config:inspect --only-error --detail + - name: Show site URL run: | echo $(docker compose exec phpfpm vendor/bin/drush --uri=http://$(docker compose port nginx 8080) user:login) @@ -135,6 +167,8 @@ jobs: # Keep our local settings (cf. # https://github.com/actions/checkout?tab=readme-ov-file#usage) clean: false + # Needed to diff config against the base branch. + fetch-depth: 0 - name: Start docker and install dependencies run: | @@ -144,6 +178,30 @@ jobs: # Important: Use --no-interaction to make https://getcomposer.org/doc/06-config.md#discard-changes have effect. docker compose exec phpfpm composer install --no-interaction + - name: Run database updates + run: | + docker compose exec phpfpm vendor/bin/drush updatedb --yes + + - name: Check that config changed by updates is exported + run: | + # After running updates, but before importing config, the active + # config may only differ from the config in code where this pull + # request changes the config. Any other difference comes from an + # update hook changing config that has not been exported. + # Collection prefixes (e.g. "language.da:") and directories are + # stripped so names from both sides compare. + git diff --name-only origin/${{ github.base_ref }} -- config/ \ + | sed -E 's#^.*/##; s#\.yml$##' | sort -u > changed-by-pr.txt + docker compose exec -T phpfpm vendor/bin/drush config:status --format=json \ + | jq -r 'if type == "object" then keys[] else empty end' \ + | sed -E 's#^.*:##' | sort -u > changed-in-db.txt + comm -23 changed-in-db.txt changed-by-pr.txt > not-exported.txt + if [ -s not-exported.txt ]; then + echo "::error::Config changed by update hooks has not been exported:" + cat not-exported.txt + exit 1 + fi + - name: Update site run: | docker compose exec phpfpm vendor/bin/drush deploy --yes From 6a70a51d83859ac921a02b3826ba61d11459fde2 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 28 Oct 2026 09:02:35 +0100 Subject: [PATCH 2/6] docs: add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0733eb3f..29c8b2bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ Changelog", this file is a reverse-chronological list of merged pull requests. ## Open PR's +- [PR-160](https://github.com/itk-dev/devops_itkdev-docker/pull/160) - 2026-10-28 - + Drupal site workflow checks that config exports unchanged, that update + hooks leave no unexported config, and (with `drupal/config_inspector`) + that config matches its schema - [PR-159](https://github.com/itk-dev/devops_itkdev-docker/pull/159) - 2026-10-26 - Switched the Drupal and Symfony templates to `nginx/nginx-unprivileged`, as `nginxinc/nginx-unprivileged` is no longer maintained From 274fc6de31bc2325347687e28f3b8db8b0526fa2 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 28 Oct 2026 09:14:24 +0100 Subject: [PATCH 3/6] fix: run schema check independently and neutral drift message --- github/workflows/drupal/site.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/github/workflows/drupal/site.yaml b/github/workflows/drupal/site.yaml index 84d51dcd..eba5502f 100644 --- a/github/workflows/drupal/site.yaml +++ b/github/workflows/drupal/site.yaml @@ -83,6 +83,8 @@ jobs: test -z "$(git status --porcelain config/)" - name: Check that config matches its schema + # Report schema errors even if the config is not up to date. + if: ${{ !cancelled() }} run: | if ! docker compose exec phpfpm composer show drupal/config_inspector > /dev/null 2>&1; then echo "::notice::drupal/config_inspector is not installed; skipping config schema check" @@ -197,7 +199,7 @@ jobs: | sed -E 's#^.*:##' | sort -u > changed-in-db.txt comm -23 changed-in-db.txt changed-by-pr.txt > not-exported.txt if [ -s not-exported.txt ]; then - echo "::error::Config changed by update hooks has not been exported:" + echo "::error::Active config differs from config/sync (not changed by this pull request), e.g. config changed by update hooks that has not been exported:" cat not-exported.txt exit 1 fi From 958d4d28312db535097eaacfbbf3fd46fad943f9 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 28 Oct 2026 09:19:15 +0100 Subject: [PATCH 4/6] fix: skip translation downloads on CI installs --- github/workflows/drupal/site.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/github/workflows/drupal/site.yaml b/github/workflows/drupal/site.yaml index eba5502f..8ea415a1 100644 --- a/github/workflows/drupal/site.yaml +++ b/github/workflows/drupal/site.yaml @@ -69,6 +69,8 @@ jobs: Date: Wed, 28 Oct 2026 09:40:23 +0100 Subject: [PATCH 5/6] feat: fail composer install in templates when a patch does not apply --- CHANGELOG.md | 3 ++- github/workflows/composer.yaml | 2 +- github/workflows/drupal-module/php.yaml | 2 +- github/workflows/drupal/php.yaml | 2 +- github/workflows/drupal/site.yaml | 6 +++--- github/workflows/symfony/php.yaml | 2 +- github/workflows/twig.yaml | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c8b2bd..b2bb474b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ Changelog", this file is a reverse-chronological list of merged pull requests. - [PR-160](https://github.com/itk-dev/devops_itkdev-docker/pull/160) - 2026-10-28 - Drupal site workflow checks that config exports unchanged, that update hooks leave no unexported config, and (with `drupal/config_inspector`) - that config matches its schema + that config matches its schema. `composer install` in the workflow + templates fails when a composer patch does not apply - [PR-159](https://github.com/itk-dev/devops_itkdev-docker/pull/159) - 2026-10-26 - Switched the Drupal and Symfony templates to `nginx/nginx-unprivileged`, as `nginxinc/nginx-unprivileged` is no longer maintained diff --git a/github/workflows/composer.yaml b/github/workflows/composer.yaml index b50dadbb..7f0add40 100644 --- a/github/workflows/composer.yaml +++ b/github/workflows/composer.yaml @@ -63,7 +63,7 @@ jobs: docker network create frontend - run: | - docker compose run --rm phpfpm composer install + docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install docker compose run --rm phpfpm composer normalize --dry-run composer-audit: diff --git a/github/workflows/drupal-module/php.yaml b/github/workflows/drupal-module/php.yaml index 7d9d5304..d4d284c9 100644 --- a/github/workflows/drupal-module/php.yaml +++ b/github/workflows/drupal-module/php.yaml @@ -61,5 +61,5 @@ jobs: docker network create frontend - run: | - docker compose run --rm phpfpm composer install + docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install docker compose run --rm phpfpm vendor/bin/phpcs diff --git a/github/workflows/drupal/php.yaml b/github/workflows/drupal/php.yaml index 7d9ec7b2..14aeddf2 100644 --- a/github/workflows/drupal/php.yaml +++ b/github/workflows/drupal/php.yaml @@ -61,5 +61,5 @@ jobs: docker network create frontend - run: | - docker compose run --rm phpfpm composer install + docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install docker compose run --rm phpfpm vendor/bin/phpcs diff --git a/github/workflows/drupal/site.yaml b/github/workflows/drupal/site.yaml index 8ea415a1..c92a54d8 100644 --- a/github/workflows/drupal/site.yaml +++ b/github/workflows/drupal/site.yaml @@ -60,7 +60,7 @@ jobs: docker compose up --detach # Important: Use --no-interaction to make https://getcomposer.org/doc/06-config.md#discard-changes have effect. - docker compose exec phpfpm composer install --no-interaction + docker compose exec --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install --no-interaction - name: Install site run: | @@ -142,7 +142,7 @@ jobs: docker compose up --detach # Important: Use --no-interaction to make https://getcomposer.org/doc/06-config.md#discard-changes have effect. - docker compose exec phpfpm composer install --no-interaction + docker compose exec --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install --no-interaction - name: Install site run: | @@ -182,7 +182,7 @@ jobs: docker compose up --detach # Important: Use --no-interaction to make https://getcomposer.org/doc/06-config.md#discard-changes have effect. - docker compose exec phpfpm composer install --no-interaction + docker compose exec --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install --no-interaction - name: Run database updates run: | diff --git a/github/workflows/symfony/php.yaml b/github/workflows/symfony/php.yaml index 6326d8ef..c3b17cd1 100644 --- a/github/workflows/symfony/php.yaml +++ b/github/workflows/symfony/php.yaml @@ -61,6 +61,6 @@ jobs: docker network create frontend - run: | - docker compose run --rm phpfpm composer install + docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install # https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/usage.rst#the-check-command docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/github/workflows/twig.yaml b/github/workflows/twig.yaml index ffb7055d..bf2ba3a1 100644 --- a/github/workflows/twig.yaml +++ b/github/workflows/twig.yaml @@ -51,5 +51,5 @@ jobs: docker network create frontend - run: | - docker compose run --rm phpfpm composer install + docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install docker compose run --rm phpfpm vendor/bin/twig-cs-fixer lint From 1aa1fa5bb56b433eedfc1620a7fc1ab29b6945c9 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 28 Oct 2026 09:42:21 +0100 Subject: [PATCH 6/6] revert: drop per-call COMPOSER_EXIT_ON_PATCH_FAILURE Projects set composer-exit-on-patch-failure in composer.json instead, which covers CI, local and release builds in one place. This reverts commit 594f19e. --- CHANGELOG.md | 3 +-- github/workflows/composer.yaml | 2 +- github/workflows/drupal-module/php.yaml | 2 +- github/workflows/drupal/php.yaml | 2 +- github/workflows/drupal/site.yaml | 6 +++--- github/workflows/symfony/php.yaml | 2 +- github/workflows/twig.yaml | 2 +- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2bb474b..29c8b2bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,7 @@ Changelog", this file is a reverse-chronological list of merged pull requests. - [PR-160](https://github.com/itk-dev/devops_itkdev-docker/pull/160) - 2026-10-28 - Drupal site workflow checks that config exports unchanged, that update hooks leave no unexported config, and (with `drupal/config_inspector`) - that config matches its schema. `composer install` in the workflow - templates fails when a composer patch does not apply + that config matches its schema - [PR-159](https://github.com/itk-dev/devops_itkdev-docker/pull/159) - 2026-10-26 - Switched the Drupal and Symfony templates to `nginx/nginx-unprivileged`, as `nginxinc/nginx-unprivileged` is no longer maintained diff --git a/github/workflows/composer.yaml b/github/workflows/composer.yaml index 7f0add40..b50dadbb 100644 --- a/github/workflows/composer.yaml +++ b/github/workflows/composer.yaml @@ -63,7 +63,7 @@ jobs: docker network create frontend - run: | - docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install + docker compose run --rm phpfpm composer install docker compose run --rm phpfpm composer normalize --dry-run composer-audit: diff --git a/github/workflows/drupal-module/php.yaml b/github/workflows/drupal-module/php.yaml index d4d284c9..7d9d5304 100644 --- a/github/workflows/drupal-module/php.yaml +++ b/github/workflows/drupal-module/php.yaml @@ -61,5 +61,5 @@ jobs: docker network create frontend - run: | - docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install + docker compose run --rm phpfpm composer install docker compose run --rm phpfpm vendor/bin/phpcs diff --git a/github/workflows/drupal/php.yaml b/github/workflows/drupal/php.yaml index 14aeddf2..7d9ec7b2 100644 --- a/github/workflows/drupal/php.yaml +++ b/github/workflows/drupal/php.yaml @@ -61,5 +61,5 @@ jobs: docker network create frontend - run: | - docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install + docker compose run --rm phpfpm composer install docker compose run --rm phpfpm vendor/bin/phpcs diff --git a/github/workflows/drupal/site.yaml b/github/workflows/drupal/site.yaml index c92a54d8..8ea415a1 100644 --- a/github/workflows/drupal/site.yaml +++ b/github/workflows/drupal/site.yaml @@ -60,7 +60,7 @@ jobs: docker compose up --detach # Important: Use --no-interaction to make https://getcomposer.org/doc/06-config.md#discard-changes have effect. - docker compose exec --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install --no-interaction + docker compose exec phpfpm composer install --no-interaction - name: Install site run: | @@ -142,7 +142,7 @@ jobs: docker compose up --detach # Important: Use --no-interaction to make https://getcomposer.org/doc/06-config.md#discard-changes have effect. - docker compose exec --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install --no-interaction + docker compose exec phpfpm composer install --no-interaction - name: Install site run: | @@ -182,7 +182,7 @@ jobs: docker compose up --detach # Important: Use --no-interaction to make https://getcomposer.org/doc/06-config.md#discard-changes have effect. - docker compose exec --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install --no-interaction + docker compose exec phpfpm composer install --no-interaction - name: Run database updates run: | diff --git a/github/workflows/symfony/php.yaml b/github/workflows/symfony/php.yaml index c3b17cd1..6326d8ef 100644 --- a/github/workflows/symfony/php.yaml +++ b/github/workflows/symfony/php.yaml @@ -61,6 +61,6 @@ jobs: docker network create frontend - run: | - docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install + docker compose run --rm phpfpm composer install # https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/usage.rst#the-check-command docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/github/workflows/twig.yaml b/github/workflows/twig.yaml index bf2ba3a1..ffb7055d 100644 --- a/github/workflows/twig.yaml +++ b/github/workflows/twig.yaml @@ -51,5 +51,5 @@ jobs: docker network create frontend - run: | - docker compose run --rm --env COMPOSER_EXIT_ON_PATCH_FAILURE=1 phpfpm composer install + docker compose run --rm phpfpm composer install docker compose run --rm phpfpm vendor/bin/twig-cs-fixer lint