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

diff --git a/core/lib/Drupal/Core/Recipe/ConfigConfigurator.php b/core/lib/Drupal/Core/Recipe/ConfigConfigurator.php
index ad837d5dc35..bb0f240ae9c 100644
--- a/core/lib/Drupal/Core/Recipe/ConfigConfigurator.php
+++ b/core/lib/Drupal/Core/Recipe/ConfigConfigurator.php
@@ -43,15 +43,18 @@ public function __construct(public readonly array $config, string $recipe_direct
     // active storage.
     foreach ($strict_list as $config_name) {
       if ($active_data = $active_configuration->read($config_name)) {
+        $recipe_data = $recipe_storage->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']);
+        unset($active_data['_core']);
+        if (empty($recipe_data['dependencies'])) {
+          unset($recipe_data['dependencies']);
+        }
         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']);
+        if (!array_key_exists('uuid', $recipe_data)) {
+          unset($active_data['uuid']);
         }
         // Ensure we don't get a false mismatch due to differing key order.
         // @todo When https://www.drupal.org/project/drupal/issues/3230826 is

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Recipes that install configuration with a predefined UUID (which is not recommended except in rare cases) will no longer incorrectly trigger RecipePreExistingConfigException.

Comments

wim leers created an issue. See original summary.

wim leers’s picture

wim leers’s picture

Issue summary: View changes
Status: Active » Needs review
Issue tags: +DX (Developer Experience)
mxr576’s picture

Version: 11.2.x-dev » 11.x-dev
smustgrave’s picture

Status: Needs review » Active

Don't see anything to review?

wim leers’s picture

Priority: Normal » Major

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..a7ffecfc5 100644
--- a/tests/fixtures/recipes/test_site/recipe.yml
+++ b/tests/fixtures/recipes/test_site/recipe.yml
@@ -8,10 +8,7 @@ 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
+  strict: true
   import:
     experience_builder: '*'
     system:

… which should also make things for Drupal CMS a more predictable.

And I can't imagine us being the only ones affected, so bumping priority 😇

phenaproxima’s picture

Assigned: Unassigned » phenaproxima
wim leers’s picture

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.

phenaproxima’s picture

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.

We could remove those keys from the recipe data during the comparison, but if the recipe data still has those keys, they'll get imported anyway...which could be bad. Config UUIDs are expected to be unique per-site, and allowing them to be imported as-is would break that.

I think what needs to happen here is that RecipeConfigStorageWrapper should always 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.

phenaproxima’s picture

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?

thejimbirch’s picture

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

wim leers’s picture

Assigned: phenaproxima » wim leers
Status: Active » Postponed (maintainer needs more info)

#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 #3492722: Update XB to require Drupal 11.2 landed, so first double-checking whether this is truly still needed. Although … #9 will definitely remain true 😇, but then it probably would only be Normal.

Doing that double-checking in #3542282: [upstream] Update all of XB's (test) recipes use `strict: true`

wim leers’s picture

Assigned: wim leers » phenaproxima
Status: Postponed (maintainer needs more info) » Active

Setting strict: true still causes the recipe to fail (but now on a different piece of config). Quoting #3542282-3: [upstream] Update all of XB's (test) recipes use `strict: true`:

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...)

wim leers’s picture

Update for #13: the urgency is gone as of https://git.drupalcode.org/project/canvas/-/merge_requests/83#note_590187 — #3532265: Remove the `canvas_dev_standard` module, instead recommend using XB's `test_site` recipe for development seems to have somehow been causing this.

Which IMHO makes this not major, because that was relatively atypical. But it does point out a weakness in the recipe system, so "minor" seems inappropriate too.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.