Problem/Motivation
Let's say you have a recipe like this one:
name: Test
install:
- user
config:
import:
user:
- user.role.anonymous
actions:
user.role.anonymous:
grantPermission: 'do something cool'
If you try to apply this on an installed site, and the anonymous user role doesn't look exactly like what's in core/modules/user/config/install/user.role.anonymous.yml, you'll get a validation error.
This is because the recipe system is currently way too strict about comparing the active config to the config that the recipe wants to import, to the point where it interferes with the recipe author's intent.
This example recipe doesn't really care about what the anonymous role looks like. It will grant a permission to it, but its other details are irrelevant. It just needs to be sure the anonymous role exists. But there's no way to do that, right now, except to have the recipe define the anonymous role using the createIfNotExists config action, which is a crappy, inelegant workaround.
If you'll permit me a little role-play here: it's already possible to say this:
- If the anonymous role exists, it has to look EXACTLY like what the User module ships.
- Assuming it does, we're all good! Let's proceed.
But what we really want here is:
- If the anonymous role exists, great! I don't care what it looks like; we're all good. Let's proceed.
- If it doesn't exist, please import it from the User module so we can proceed.
Proposed resolution
Let's introduce some lightweight, explicit syntax to make it clear what to do with existing config.
We'll support a new strict key under the config section of recipe.yml. It be one of three things:
true, meaning that all of the config listed in theimportsection, and in the recipe's config directory, must match what's in active config.false, meaning that the config listed inimport, and in the recipe's config directory, does not need to match what's in active config. It's fine if it does, but it doesn't have to.- an array of config names that need to match what's in active config
For example:
config:
import:
user:
- user.role.anoymous
metatag: '*'
strict:
- metatag.defaults.global
This says:
- We're going to get the anonymous role from the User module, if it doesn't already exist. If it does exist, we'll just use what we already have.
- We're going to get everything we can from the Metatag module. If we already have some stuff from Metatag in the DB, we'll keep those things intact.
- But metatag.defaults.global, if we already have it in the database, HAS to be identical to what Metatag provides by default! If it's not, then that's an error.
To clarify the difference here:
importtells us what config to bring in, and where to find it.stricttells us what to do if any of that config already exists in the database.
Issue fork drupal-3478332
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:
- 3478332-recipes-imported-config
changes, plain diff MR !9734
Comments
Comment #2
phenaproximaComment #3
thejimbirch commentedI believe you captured all that we discussed.
Comment #4
phenaproximaJealously grabbing this issue for myself. :)
Comment #6
phenaproximaNeeds tests, but the overall approach is ready for review.
Comment #7
phenaproximaThis is affecting Drupal CMS's recipe construction and testing, so tagging as a Starshot blocker.
Comment #8
phenaproximaOne thing I realized we haven't thought about is what to do with lines like:
Should all that config be treated strictly? Or more relaxed?
I wonder if we should do something like this to opt it into strict mode:
My only concern is that this syntax starts to look arcane and, well, Unix-ey. I don't want recipes to start looking like shell scripts. I wonder if we could do something like this, then:
And then later on we could change the
*token to justall, which would be a synonym for?all.Thoughts?
Comment #9
quietone commentedComment #10
nicxvan commentedOne tangential concern I have about this syntax is the !include convention is used by at least one prominent python project in order to include additional files.
I can see wanting to split actions into separate files in the future to keep things organized and it might be nice to match that syntax if a parser is written.
https://community.home-assistant.io/t/use-of-include-of-additional-yaml-... as an example.
I agree this issue is super helpful, just thinking out loud about the use of: !
Comment #11
nicxvan commentedWhat if we added another layer
(Sorry on my phone)
Or
Default or no key could be lenient for backwards compatibility.
But then there are no additional bash like special characters.
Comment #12
phenaproximaI'm open to considering a different syntax.
The reason I went for ! is because it's reminiscent of
!importantin CSS. To me, it conveys urgency and primacy. "Pay attention, this matters to me!"It's the opposite of the question mark, which conveys ambiguity. "I might need this, I might not." I think the same reasoning is why PHP has the ? for nullable types, and ?-> as the nullsafe operator.
If recipes ever were able to include other files, then I'm guessing we'd go for a syntax like `@include` or something like that. Dunno.
You're right that using single characters like ! and ? does add a little cognitive load. We could maybe do something like this instead, for clarity:
Comment #13
nicxvan commented! And ? Make more sense spelled out but I think nesting like my second comment is more explicit and easier to remember.
My first thought when seeing ! was the not operator so people might think it's an exclusion.
Comment #14
phenaproximaOooh, that's a great point - definitely a strong reason to consider some other way to opt into strict comparison.
Comment #15
nicxvan commentedI think the only benefit for:
Over
Is not having to type strict for each line of you have multiple.
But it's probably safer to need to specify strict on every config you need strict validation.
Comment #16
phenaproximaHow about this as an idea:
What if we made the comparisons lenient by default, but then allowed an explicit list of config to be compared strictly (more or less as you suggested)? You'd be looking at syntax like this:
This would get us where we need to go, with no special new syntax. It lets the intent be explicit. And it would be extremely simple to implement.
I kinda love this idea.
Comment #17
nicxvan commentedDeleted: Duplicate comment of 15
Comment #18
nicxvan commentedI like the structure in 16!
Only thought is maybe import and strictImport?
Comment #19
phenaproximaThe only argument I can think of against
strict_importis that it would apply to stuff in the recipe'sconfigdirectory too, not just the stuff listed inimport. But I don't feel strongly.Comment #20
mandclu commentedOne idea: maybe instead of
importandstrict_import, we could sayimport(always strict) andensure(which is lenient)?Comment #21
ironnuts commentedYes and in bash shell there is -f ie --force. Which is pretty self-explanatory. So the command might or might not work but with --force it will work for sure. So I am speculating that the word 'force' could be used in this new scheme. It is short and sweet?
Maybe not as strict is not the same concept. But could a flag be passed like --strict? Characters like '!' and '?' are used in different contexts like now in PHP 8 in typehinting. Maybe they are overused and best avoided as the brain tries to revolve through all the different contexts trying to figure out which might apply. Even though as they say 'brevity is the source of wit.'
Comment #22
nicxvan commentedI'm not sure ensure is as clear as strict.
Also @oily, force implies that it will impose the structure no matter what, where in this case if it can't it will fail. Also this isn't about running the command so you can pass it, it's for the recipe authors to determine the importance of particular config.
Comment #23
phenaproximaWe're sort of derailing the issue, but we've already got plans for what "force" means, and it will be added in another issue.
Comment #24
ironnuts commented@phenaproxima May the "force" be with you!
Comment #25
phenaproximaStill needs work.
Comment #27
phenaproxima@nicxvan, @alexpott, @thejimbirch, and I discussed this in Slack and we found a better syntax. I'm updating the issue summary to reflect the proposal.
Comment #28
phenaproximaComment #29
phenaproximaWe're happy with the design and tests are written (and passing!)...I think this is truly ready for review.
The one thing to note: at the moment, this MR keeps the existing strict-by-default behavior. Should we change that now? Or in a follow-up? It feels a little bit like follow-up material since it is a behavior change and most likely warrants a change record. But, happy to go either way.
Comment #30
phenaproximaWrote a change record: https://www.drupal.org/node/3478662
Comment #31
phenaproximaComment #32
phenaproximaComment #33
thejimbirch commentedAll threads resolved, well commented and has tests.
This is a great step forward for recipe authors and leniency.
Marking as RTBC.
Comment #34
alexpottAdded a couple of comments to the MR.
Comment #35
phenaproximaComment #36
thejimbirch commentedFeedback addressed. Back to Alex.
Comment #37
alexpottCommitted and pushed f0fda63b58 to 11.x and fcf7d526b3 to 10.4.x. Thanks!
Comment #40
liam morlandOne problem related to this is that #3480248: Drupal does not recognize when the config is identical. I have applied a recipe and then immediately applied it again was told that the config was not identical.
There are more cases that need to be handled than just strict or not strict. I wrote about four possibilities, replace, update, ignore, and error, in #3478669-5: [pp-1] Make recipes' comparison with existing config lenient by default.
Comment #41
liam morlandComment #43
thejimbirch commented