\Drupal\Core\Recipe\ConfigConfigurator unsets the uuid of active configuration, but it should only do that if the recipe data does not contain a UUID

Created on 25 June 2025, about 2 months ago

Problem/Motivation

\Drupal\Core\Recipe\ConfigConfigurator does:

      if ($active_data = $active_configuration->read($config_name)) {
        // @todo https://www.drupal.org/i/3439714 Investigate if there is any
        //   generic code in core for this.
        unset($active_data['uuid'], $active_data['_core']);
        if (empty($active_data['dependencies'])) {
          unset($active_data['dependencies']);
        }
        $recipe_data = $recipe_storage->read($config_name);
        if (empty($recipe_data['dependencies'])) {
          unset($recipe_data['dependencies']);
        }
        // Ensure we don't get a false mismatch due to differing key order.
        // @todo When https://www.drupal.org/project/drupal/issues/3230826 is
        //   fixed in core, use that API instead to sort the config data.
        self::recursiveSortByKey($active_data);
        self::recursiveSortByKey($recipe_data);
        if ($active_data !== $recipe_data) {
          throw new RecipePreExistingConfigException($config_name, sprintf("The configuration '%s' exists already and does not match the recipe's configuration", $config_name));
        }

But that means that if:

  1. the recipe data contains a UUID (which is not a best practice, but it's also not forbidden, and sometimes, there may be a good reason for it)
  2. the active data's UUID matches but has been removed

that Recipes will WRONGLY throw this exception!

Discovered at #3532130-11: `/xb/api/v0/config/component` crashes when `article` node type does not exist due to hardcoded assumption in `GeneratedFieldExplicitInputUxComponentSourceBase::getClientSideInfo()` β†’ .

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

πŸ› Bug report
Status

Active

Version

11.2 πŸ”₯

Component

recipe system

Created by

πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Comments & Activities

  • Issue created by @wim leers
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Related, but not the same: πŸ› Drupal does not recognize when the config is identical Active .

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • πŸ‡­πŸ‡ΊHungary mxr576 Hungary
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Don't see anything to review?

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    FYI this would allow XB's test recipes to become more reliable:

    
    diff --git a/tests/fixtures/recipes/test_site/recipe.yml b/tests/fixtures/recipes/test_site/recipe.yml
    index c776203de..d476bf6a1 100644
    --- a/tests/fixtures/recipes/test_site/recipe.yml
    +++ b/tests/fixtures/recipes/test_site/recipe.yml
    @@ -8,10 +8,6 @@ install:
       - xb_test_sdc
       - xb_dev_standard
     config:
    -  # Loose config imports because the code components in `xb_test_code_components` contain UUIDs, which are used to
    -  # generate corresponding assets on disk with predictable names.
    -  # @todo Change to `strict: true` after core bug https://www.drupal.org/project/drupal/issues/3532271 is fixed.
    -  strict: false
       import:
         experience_builder: '*'
         system:
    … which should also make things for Drupal CMS a whole lot more predictable :innocent: 
    

    And I can't imagine us being the only ones affected, so bumping priority πŸ˜‡

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    How did I end up reporting this?

    See #3532130-11: `/xb/api/v0/config/component` crashes when `article` node type does not exist due to hardcoded assumption in `GeneratedFieldExplicitInputUxComponentSourceBase::getClientSideInfo()` β†’ for what prompted me to add strict: false to XB's test site recipe and also prompted the creation of this issue.

    The 3 commits before that comment show what had to change.

    The problem IIRC is that the recipe system when strict: true (the default), the comparison of the UUID stored in tests/fixtures/recipes/test_site/config/experience_builder.component.js.xb_test_code_components_using_imports.yml fails to match the active one.

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Is there any reason we shouldn't just unconditionally remove the uuid and _core keys from the incoming recipe data?

    The recipe system assumes those keys aren't there, but that's not a safe assumption -- lots of people make that mistake. We definitely do not want to have those keys be considered in the comparison.

    On the other hand, we could remove those keys from the recipe data during the comparison, but if the recipe still has those keys, they'll get imported anyway...which could be bad.

    I think what needs to happen here is that RecipeConfigStorageWrapper should unconditionally remove those keys from any config it reads in. Either that, or the FileStorage object created by \Drupal\Core\Recipe\ConfigConfigurator::getConfigStorage() needs to do it.

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    the recipe data contains a UUID (which is not a best practice, but it's also not forbidden, and sometimes, there may be a good reason for it)

    Wim, can you elaborate on this bit? It's not forbidden but that might just be an oversight on our part. What are some good reasons for a recipe to include config UUIDs?

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    What are the rare cases where you would want/need the UUID in a config?

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    #9++

    (which is not a best practice, but it's also not forbidden, and sometimes, there may be a good reason for it)

    What I meant is that config exported as default config into a module is (AFAIK) supposed to NOT have a UUID β€” default config in modules is supposed to get random UUIDs generated β€” because all that should matter is the config entity ID (a machine name), not the UUID.

    What are the rare cases where you would want/need the UUID in a config?

    See XB's \Drupal\experience_builder\Entity\XbAssetLibraryTrait::getJsPath() β€” where it needs to generate a predictable file name based on the config entity UUID.

    Lots has changed/been improved/hardened in XB itself and in Drupal core 11.2 β€” this issue was created before πŸ› PHPUnit Next Major tests failing Active landed, so first double-checking whether this is truly still needed. Although … #9 will definitely remain true πŸ˜‡, but then it probably would only be .

    Doing that double-checking in πŸ“Œ Try making XB's `test_site` Check if #3532271 is still blocking Active …

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Setting strict: true still causes the recipe to fail (but now on a different piece of config). Quoting #3542282-3: Try making XB's `test_site` Check if #3532271 is still blocking β†’ :

    Error: Command failed: sudo -u www-data DRUPAL_DEV_SITE_PATH=sites/simpletest/42352967 php core/scripts/drupal recipe /builds/project/experience_builder/_drupal/web/../web/modules/contrib/experience_builder/tests/fixtures/recipes/test_site
    In ConfigConfigurator.php line 68:
                                                                                   
      The configuration 'experience_builder.xb_asset_library.global' exists alrea  
      dy and does not match the recipe's configuration                             
                                                                                   
    recipe [-i|--input INPUT] [--] <path>
        at ChildProcess.exithandler (node:child_process:422:12)
        at ChildProcess.emit (node:events:517:28)
        at maybeClose (node:internal/child_process:1098:16)
        at Process.ChildProcess._handle.onexit (node:internal/child_process:303:5)
        at Process.callbackTrampoline (node:internal/async_hooks:128:17) {
      code: 1,
      killed: false,
      signal: null,
      cmd: 'sudo -u www-data DRUPAL_DEV_SITE_PATH=sites/simpletest/42352967 php core/scripts/drupal recipe /builds/project/experience_builder/_drupal/web/../web/modules/contrib/experience_builder/tests/fixtures/recipes/test_site',
      stdout: '',
      stderr: '\n' +
        'In ConfigConfigurator.php line 68:\n' +
        '                                                                               \n' +
        "  The configuration 'experience_builder.xb_asset_library.global' exists alrea  \n" +
        "  dy and does not match the recipe's configuration                             \n" +
        '                                                                               \n' +
        '\n' +
        'recipe [-i|--input INPUT] [--] <path>\n' +
        '\n'
    }
    

    β€” https://git.drupalcode.org/project/experience_builder/-/jobs/6267192#L1235

    (That's the test results for https://git.drupalcode.org/project/experience_builder/-/merge_requests/1...)

Production build 0.71.5 2024