Problem/Motivation

\Drupal\Core\Recipe\Recipe has a TODO about improving the validation of the contents and structure of recipe.yml files. Let's beef that up.

Proposed resolution

PHP-TUF uses Symfony's Validator component to set up a tree of constraints to validate the data of incoming JSON files. An example of this: https://github.com/php-tuf/php-tuf/blob/main/src/Metadata/TargetsMetadat...

Let's do something similar here.

Remaining tasks

Implement it and add unit tests to prove that all constraints are exercised.

As discussed in #3413824: Warn Recipe developers if a module being installed does not have a corresponding config import declaration, one of the most important things to validate is that every module listed in the modules section of a recipe also has a corresponding entry in the config:import section, specifying whether to import some, all, or none of the module's config.

Validate that config actions should have valid config name in case of wildcard config actions. See - https://git.drupalcode.org/project/distributions_recipes/-/merge_request...

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

phenaproxima created an issue. See original summary.

phenaproxima’s picture

Status: Active » Needs review

Wim Leers made their first commit to this issue’s fork.

wim leers’s picture

Status: Needs review » Needs work
Issue tags: +validation
Related issues: +#3311111: Support modern syntax for defining module dependencies

First: WOW! 🤩

Second: it doesn't quite work perfectly yet:

Drupal\KernelTests\Core\Recipe\RecipeRunnerTest::testConfigFromModule
Drupal\Core\Recipe\RecipeFileException: Array[config][import][0]:
    This field is missing. (code 2fa2158c-2a7f-484b-98aa-975522539ff8)
Array[config][import][config_test]:
    This field was not expected. (code 7703c766-b5d5-4cef-ace7-ae0dd82304e9)

… so we'll need to adjust what you have in there so far. But it's a superb start!

The beauty of this approach is that we can reuse the very same validation constraints that we're already using for config 👍

Validation expanded to confirm this works

  1. For example, the values under install should not just be non-empty strings. They should conform to what Drupal considers valid extension names. For that, #3324150: Add validation constraints to config_entity.dependencies added the ExtensionName constraint 😄

    This was wrong, because due to #3311111: Support modern syntax for defining module dependencies, syntax like drupal:forum are supported, to allow specifying either drupal:forum or forum:forum.

  2. recipes ✅ The ExtensionName constraint can be used for the values under this key — done 👍
  3. name ✅ Matched the constraints used for type: label in Drupal core

Its now back to the same set of failures as before I started contributing. I'll let @phenaproxima figure out how to deal with the remaining things 🤓

Importance for recipe authoring to get precise feedback

Since the DX of authoring a new recipe is essentially 100% manual YAML construction today, and it will be for a good while to come, I think it's crucial that we have test coverage proving that every small mistake a recipe author might make is in fact caught by validation.

I just pushed a first test case for this.

Prior art/pattern to copy to thoroughly test this?

I have some prior art/experience with this very problem, because I faced the same challenge for CKEditor 5 plugin definitions. However, that one is far more complicated because it needs to mock modules. Since validating a recipe happens entirely outside the context of a running Drupal site, it'll be far simpler here. What matters IMHO is to not have dozens of recipe.yml files in the codebase, because that'll be chaotic very quickly. I think it'd be easier to have them defined in the test provider itself.

See \Drupal\Tests\ckeditor5\Kernel\CKEditor5PluginManagerTest::providerTestInvalidPluginDefinitions() for an example. (Although the value is arguably less here, since there's only a YAML file for each test case, whereas for CKEditor 5 you'd need the entire module directory structure + *.info.yml file.)

@alexpott: would you prefer actual YAML files on disk, like in the first test case I added, or would you prefer the approach taken in CKEditor 5?

thejimbirch’s picture

What matters IMHO is to not have dozens of recipe.yml files in the codebase, because that'll be chaotic very quickly. I think it'd be easier to have them defined in the test provider itself.

Are you talking about managing recipe.yml files in different places or in the core/tests/fixtures/recipes folder?

I like the idea of having actual recipes in there to provide examples in the code base, not just in documentation places.

wim leers’s picture

Yes, correct recipes make sense to have actual Recipe YAML files.

In this case, we're working to detect invalid Recipe YAML files. Those I'd argue should have actual YAML files on disk. 😊

wim leers’s picture

Oh and this actually resolved

// @todo validate structure of $config['import'] and $config['actions'].

in \Drupal\Core\Recipe\ConfigConfigurator::__construct() too! 🤩

wim leers’s picture

Status: Needs work » Needs review

Before going much further here, I'd like to get feedback from @alexpott.

borisson_’s picture

I really like that we are reusing the symfony validator to ensure that everything is correct, I think the approach here is good. We will of course need more/better tests.

wim leers’s picture

Priority: Normal » Major
phenaproxima’s picture

Issue summary: View changes
wim leers’s picture

@phenaproxima++ for #12.

This is crucial for recipe creators's usability/DX (whichever you want to call it).

narendrar’s picture

One issue which I found while working on #3417835: Convert the Standard install profile into a set of recipes is adding wrong key to config in recipe.yml. I think currently config expects config:import or/and config:actions and any other key suppose config:install does nothing. So should we validate that config should not have any other key except 2 defined?

wim leers’s picture

Yes, we should. Any and every guardrail we can add that would make a problem explicit (rather than the infrastructure silently accepting it and needing a debugger to figure out) is worth adding 👍

narendrar’s picture

In continuation to #14, recipes keys are name, description, type, recipes, install, config and content and defining any other key should give validation error.

wim leers’s picture

#16: the MR already has validation for all those 😄 (and #14's suggestion too btw) — we just need to finish what @phenaproxima started! 🤞

narendrar’s picture

@Wim Leers, I tried to add the validation constraint, but it is not getting triggered. I think something is not correct in

      'config' => new Optional([
        new Collection([
          'import' => new Optional([
            new Required([
              new Collection([
                new All([
                  new Type('string'),
                  new NotBlank(),
                  new Regex('/^.+\./'),
                  new ConfigImportKeyExistsConstraint(),
                ]),
              ]),
            ]),
          ]),
          'actions' => new Optional([
            new Type('array'),
          ]),
        ]),
      ]),
wim leers’s picture

That's totally possible! What did stepping through the execution with a debugger reveal?

narendrar’s picture

I added ConfigImportKeyExistsConstraint to 'install' and it is working now 😬. Not sure if this is the correct place though.
If this seems correct then we might expand ConfigImportKeyExistsConstraint to validate other things like whether to import some, all, or none of the module's config.

TBH, I don't know if recipes support config:import none.

phenaproxima’s picture

Title: Robustly validate the structure of recipe.yml » [PP-1] Robustly validate the structure of recipe.yml
Status: Needs review » Postponed

I think this is blocked by #3421197: It is impossible for recipes to depend on recipes outside of their own directory.

Without that issue, we cannot validate that recipes depend on other recipes that actually exist, which I think is a critically important guard rail we should implement.

narendrar’s picture

Issue summary: View changes
wim leers’s picture

Status: Postponed » Needs work

I think there's plenty of things that can continue here? #3421197: It is impossible for recipes to depend on recipes outside of their own directory may block commit of this issue, but there's lots of work we can already do here! 😊

wim leers’s picture

This is definitely moving in the right direction 👍 Did another review round to keep this going 😊

wim leers’s picture

This is definitely going in the right direction — the only thing still missing here is more test cases! 👍

narendrar’s picture

Status: Needs work » Needs review
wim leers’s picture

Status: Needs review » Needs work

This is now looking very close! 😄

Several follow-ups have been identified — let's get those created and let's make this MR point to them. 50% of the remaining work is creating those follow-ups, 50% are for small remarks.

wim leers’s picture

Title: [PP-1] Robustly validate the structure of recipe.yml » Robustly validate the structure of recipe.yml
wim leers’s picture

There's a few threads from my feedback 2 days ago that are not yet addressed, but then again @phenaproxima is working on it as we speak! 😄

It's VERY exciting to see that this is unblocked and nearly ready 🤩

phenaproxima’s picture

Status: Needs work » Needs review

I think all feedback is addressed; some threads have been left open for Wim to sign off on.

phenaproxima’s picture

wim leers’s picture

Looks wonderful! We can nitpick exact validation messages, but I'd rather not do that here, and do it later, when Recipes is further along. Right now, this is a HUGE improvement over the status quo, and will help authors of future recipes enormously! 👏🤩

A few follow-ups have already been created (thanks @phenaproxima):

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Left a big comment about :: validateExtensionHasImportEntry() - I think the best way forward is to move that part of the validation to a follow-up.

wim leers’s picture

Status: Needs work » Reviewed & tested by the community
Related issues:
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed fa6b83c3727 to 11.x and e902f1c4937 to 10.2.x. Thanks!

  • alexpott committed e902f1c4 on 10.2.x
    Issue #3400672 by phenaproxima, narendraR, Wim Leers, alexpott: Robustly...

  • alexpott committed fa6b83c3 on 11.x
    Issue #3400672 by phenaproxima, narendraR, Wim Leers, alexpott: Robustly...

  • b1c7a467 committed on patch
    Update recipe 10.2.x patch e902f1c4 Issue #3400672 by phenaproxima,...

  • 19e0ad33 committed on patch
    Update recipe 11.x patch fa6b83c3 Issue #3400672 by phenaproxima,...

  • alexpott committed 72039e5d on 10.2.x
    Revert "Issue #3400672 by phenaproxima, narendraR, Wim Leers, alexpott:...
wim leers’s picture

Eh … what's going on here? 😨

alexpott’s picture

Version: 10.2.x-dev » 10.3.x-dev

This broke 10.2.x so I reverted and created 10.3.x :) so this is on 10.3.x and 11.x and 10.2.x is now stuck in time. I should have opened 10.3.x when core did.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.