Problem/Motivation
As a site administrator, I want to exclude certain modules from deployments, in order to not put developer tools on my production environment.
While #3048860: Create Config Environment API and the other issues around the Configuration Environment module aim to substitute Config Split, by making environments config entities and exporting their configuration as collections, it is sometimes just simpler not to share the development modules configuration and instead making sure that development modules are guaranteed from being excluded from the configuration export.
Proposed resolution
Add Config Exclude to core.
This unblocks #2991683: Move configuration transformation API in \Drupal\Core\Config namespace
Remaining tasks
Add the functionality to Config Environment transitionally and move it to core.services in #2991683: Move configuration transformation API in \Drupal\Core\Config namespace or in #3079029: Move module config exclusion from config_environment to core.services
User interface changes
none
API changes
New setting for modules that are excluded from the configuration export and remain installed on import that doesn't have them.
$settings['config_exclude_modules'] = ['devel', 'stage_file_proxy'];
Data model changes
none
Release notes snippet
A new setting allows development modules to be excluded from the configuration synchronisation. See the change notice for more details.
Comment | File | Size | Author |
---|---|---|---|
#29 | interdiff-3077504-23-29.txt | 5 KB | bircher |
#29 | 3077504-29.patch | 23.54 KB | bircher |
#23 | interdiff-3077504-21-23.txt | 4.47 KB | bircher |
#23 | 3077504-23.patch | 21.19 KB | bircher |
#21 | interdiff-3077504-19-21.txt | 7.34 KB | bircher |
Comments
Comment #2
bircherAttached is a patch porting all of config_exclude to the new transformer API.
I added it to config_environment because creating a new module requires adding a drupal.org help page and we can do that once the decision is taken that it should be its own module in core.
Comment #3
bircherI just realized I forgot the collections..
Comment #4
borisson_I think that's a good decision, they are closely tied together so keeping them in one module seems like the easiest solution.
The patch also seems to introduce a few cs violations, those should also be fixed,
not sure if we should mention development modules here. They can be other modules as well, so maybe configured modules?
We don't usually add private variables so that we extend classes, but since this is final it seems to be ok.
Maybe this should be activeStorage instead of just active? Because at first it seemed like this property was a boolean flag.
here it is excluded modules, lets use that language everywhere.
This comment should probably be the same.
Comment #5
bircherThanks for the review!
RE #4:
1) well we will have to add to the change record that this is not meant for a big amount of modules and should not be used when one wants to stage and deploy all configuration and know that what you stage is deployed. since excluded modules by design will not be uninstalled when the config doesn't include them. I changed it also with regard to 4)
2) yes it is final and private as this is not meant to be extended. We can always make it non final without breaking anything if at some point someone wants to extend it. I think we should do that more in general.
3) sure, makes sense.
4) excluded modules makes sense yes.
5) Well one could argue that it should be the same, but an export storage can never be empty unless it has been made empty which means non-functional in this case by some other event subscriber. The import on the other hand can be empty simply when the folder is empty. I am not against changing any of the comments as such if they are helpful for others.
Comment #6
bircherComment #7
borisson_Awesome, thank you for the clear response, now all this needs is the Change Record. The code already looks great.
Comment #8
marcvangendThanks for your work on this. As the author of the Config Exclude module I would obviously like to see functionality like this in core :-)
At first I wasn't sure if this class should be final, but after seeing this talk by Wim Leers (thanks Wim!) and reading #3019332: Use final to define classes that are NOT extension points I agree.
Some remarks about the code:
I'm not sure what this comment means, or what it tries to clarify here.
For consistency, I would prefer
$active_storage
instead of$active
here as well.Coding standards: This needs a description of the returned value, eg. "An array of module names". Also, "Drupal" deserves a capital "D".
Should the name of this setting be namespaced, eg. "config_environment_excluded_modules"?
Coding standards: This needs a description of the returned value, eg "An array of configuration names".
I will now go test and try to break the code :-)
Comment #9
marcvangendOK, I did some manual testing.
Steps performed on a standard-profile D8 site:
- Enable config_environment module.
- Composer-require and enable Devel module.
- Add
$settings['config_exclude_modules'] = ['devel'];
to settings.php.- Export config: full export through the admin UI, full export with Drush 9, single-item export of core.extension.yml.
- Check the result: no traces of Devel module should be present.
- Import the exported config again.
- Check the result: although the exported config does not mention Devel module, it should still be enabled.
Results:
- The ExcludedModulesEventSubscriber only kicks in during a full export in the admin UI, not during a single-item export or Drush export. Maybe this was to be expected given the current state of Drupal core and/or Drush? At least as a developer, I was expecting it to work with
drush cex
too.- The config export generated in the admin UI indeed does not contain traces of Devel module; not in core.extension.yml, nor Devel's own config files.
- When importing the config .tar.gz archive again (in the admin UI), Devel module remains enabled even though it is not in core.extension.yml.
Comment #10
marcvangendHere's a patch that fixes points 2, 3 and 5 from comment #8.
You can ignore point 1, I now see it is a response to the @todo higher up in the file.
Point is 4 open for discussion afaic.
Comment #11
bircherRE #9
Yes for drush to work you need: https://github.com/drush-ops/drush/pull/4061
The single item import and export are not doing a configuration synchronisation so they are not going to hide excluded modules but rather reflect the active configuration storage.
So your experiment reflects that this is working as expected.
#8 4) Well, I took the name config_exclude uses, if we want to add this to core as config_exclude then that would make sense as is.
#4 and the issue summary remaining task:
If we were to add this to a new module then it could immediately become stable. As a followup we can also add some messages on the status page or the config import/export pages in case we want to (config_exclude currently doesn't do that as far as I know)
If we are to put it in config_environment then we will mark it as experimental-beta-stable after this so that it will stay in 8.8 as experimental and we add the environment config entities (#3048860: Create Config Environment API) and all the rest we had planned in #3033427: [plan] Add support for environment-specific configuration with update hooks (which will not be a problem of course).
In any case this issue unblocks #2991683: Move configuration transformation API in \Drupal\Core\Config namespace
Comment #12
marcvangendThanks for the explanation Bircher. I'm happy my tests confirm it is working as expected.
Indeed if the final goal is to turn this into a separate module, the setting name doesn't need to be changed.
I like the idea to notify users about excluded config on the import/export page. (And during drush cim / cex too, I suppose.)
So we need a Change Record, and perhaps a re-roll to split this off into a separate module, but code-wise I would call this RTBC.
Comment #13
pandaski CreditAttribution: pandaski at govCMS (Australian Government Department of Finance) commentedShall this module itself be excluded or can be conditionally excluded based on the different environments?
Comment #14
bircherRE #12 yes whether to split it off in a different module or not is ultimately a decision above my pay-grade and to be honest I can see good arguments for both and I would be happy either way.
After all if you do not set this setting then nothing happens. (Do we need a test for that too?)
RE #13 I would argue that you don't have to do that, but you could of course. Maybe I would not recommend it because it will allow for a more nuanced environment control once we have the other parts and that will not work well when the environment configuration is excluded from the configuration synchronisation. (So if we put it into its own module then you can safely exclude the config_exclude module, if it stays as part of config_environment then you really shouldn't)
This leads me to another observation I made this morning while drinking my coffee: Since the list of excluded modules is in settings.php it is totally up to the user to make sure it contains sane values. For example there is nothing stopping you from excluding the system or user module, yes you can then no longer import the configuration unless you had also excluded the system or user modules on the target site and you can no longer install from that configuration. But nothing stops you from manually editing the config yamls and that can also lead to not being able to import the config.
But what I dread a bit more is that some people will think that this is a great idea for multi site which it is NOT. But that is a whole other discussion we need to have and is totally off topic here.
I guess the question is should we do something about this (like a list of modules that can never be excluded)?
I am of the opinion that we should document properly that using this feature means that you opt out of some of the consistency guarantees of the configuration synchronisation process and not actively prevent you from doing what you want to be doing.
It is maybe also important to note that however you configure this it will never break your running site, it might just not allow you to import configuration. This happens in exactly the same way as if you had manually messed up the configuration files.
Comment #15
marcvangendRe #13: I'm not sure why you would want to exclude config_exclude itself, but it is possible. I ran a quick test and found that when config_exclude is listed in $settings['config_exclude_modules'], it remains enabled and functional when you import config that does not list Config Export (actually, config_environment with the latest patch) in core.extension.yml. In other words, you could enable it on your local development environment, together with other excluded modules, without any effect on the git repo you commit your config to.
While doing that quick test I did find a minor bug: When you change
$settings['config_exclude_modules']
without making any other configuration changes, it does not take effect. This happens because a change to$settings['config_exclude_modules']
does not trigger one of the ConfigEvent events, so ExportStorageManager::onConfigChange() is not called and$this->state->set(self::NEEDS_REBUILD_KEY, TRUE);
is not executed. Back to "needs work"!Comment #16
bircherRight, I was thinking about this "caching" of the export storage and how it could be a problem when we put the api in core. But then we didn't really have a need for it and it would have made it more complex. So now I added a new event that is dispatched when the storage manager isn't sure that it needs to rebuild the storage. That addition can then be used to check if the settings have changed.
Comment #18
bircherAh I never know how to name patches or in which order to upload them so that the bot don't kick them back...
Comment #19
bircheractually some more coding standards fixes..
Comment #20
alexpottLet's not use event propagation as a signal here. Let's add an event with methods like
setRebuildNeeded()
andisRebuildNeeded()
.Comment #21
bircherYes that makes it easier to reason about it.
Comment #22
alexpottI thought about namespacing this key a bit - something like
config.exclude_modules
- but the current value is nice because it matches the settings.php key. That said there's no reason we can't make this string a class constant and use it for state and settings.Does this happen?
Why the is array check - there's the default value - it seems overly cautious.
I think this could be better named. Something like
getDependentConfigNames()
Maybe a comment above saying that now we're finding any configuration dependent on such the configuration.
Also I'm deeply suspicious that this is too simplistic looking at
\Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval()
- there are config entities that adapt themselves on module uninstall for example. I've often felt we should not allow a module to be excluded if there are these sort of indirect dependencies.Comment #23
bircherRE #22
1) Sure, I also like that the key is the same in settings and state.
2) Yes, it is empty when the sync folder is empty and even if the event would not be dispatched when empty then we still work with the storage that could have been modified by other event subscribers, so this check is to make sure that if the storage is not in a state we expect then we don't do anything.
3) Well, we use it as an array here so that was just a sort of type check, if you set the settings to a non-array value php would then complain. But we can argue that this is then the users fault for misconfiguring the site.
4) +1
5) more comments are better yes.
And about
ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval()
, well I don't have enough time to really get into what happens there, but I thinkConfigManager::findConfigEntityDependents('config' ...)
will find all of the ones in the graph, so with calling\Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval
some config would qualify not to be excluded in some altered form while we now exclude them wholesale.So while it is maybe too simplistic, I think we are on the safe side as we make sure that no config is part of the sync directory/zip that depends on a module which is excluded. This is also the algorithm that config_split uses so I don't know if it is something a lot of people encounter.
There are two roads we could go down here that I see:
A) We do more advanced filtering, we modify the configuration that can be made to not depend on the excluded modules and use that to export and we merge the config on import to add back the things that we removed. I think we could do that in a followup if we need to. Ie if there is a use case.
B) We create a concept of "modules that can not be excluded" which contains a hard coded list of system modules and some sort of dynamic check to see what effects the module has on configuration (like languages, third party settings etc) I don't know what conditions that would be or how we would check for them though.. so if that is the road we want to take then someone else needs to take over this issue or at least give a lot of specific input.
Comment #24
bircherI added the change notice here: https://www.drupal.org/node/3079028
I also had a chat with @alexpott on slack what could be done to verify which modules can be excluded or not.
The system we have to validate which modules can be uninstalled work with the
\Drupal\Core\Config\ConfigEvents::IMPORT_VALIDATE
event which is dispatched from the ConfigImporter. In order to verify that a module can be excluded would, thus, include creating a ConfigImporter just to dispatch this event. This is not necessary since the import will do that making sure that invalid configuration can not be imported. So excluding modules has the same effect as manually editing the yaml files and the ConfigImporter will assert that required modules can not be uninstalled.I also created a followup issue to discuss whether the event subscriber we add here should be added to core together with the api or remain as a feature in the experimental module.
Comment #25
borisson_The code here looks very solid and this is a very useful new feature. I think this is good addition to make the CMI-2 initiative even more better.
I can't find any reason why this shouldn't go in.
Comment #26
alexpottCan use the class constant here too.
I'm not sure that there is a test of this situation yet. It looks like only direct dependencies are being tested.
Comment #27
johnwebdev CreditAttribution: johnwebdev commentedWhat does a required module mean? A module that another module depends on that is not excluded?
Comment #28
alexpott@johndevman both system and user are required modules. I.e they have
required: true
in their .info.yml files. It might also be possible to do weird things by excluding a module that supplies a field type with data (for example telephone). But in all cases we have config import validation and the ability to tell the user exactly what is going to change on import.Comment #29
bircherRE #25 Thanks!
RE: #26
1) ah I had overlooked that, nice!
2) well that is relatively simple to test, so yea lets do that.
Comment #30
marcvangendI did some more manual testing, everything works as expected. Great work @bircher and thanks for your input @alexpott! All feedback since #25 has been addressed, so going back to RTBC.
Comment #31
larowlanAdding review credits.
Reviewed this and the only thing that I think is missing is docs in example.settings.php for the new setting.
Can we get an example entry for this in example.settings.php with some documentation of how to use it etc.
Comment #32
larowlanDiscussed ^ with @xjm and @bircher
Because this is an experimental module, we don't need to do that now, but we need to do it before the module is marked stable.
So tagging as needs follow-up - can you add a follow-up issue to add those docs to settings.php and add it to the config_environment stable roadmap?
Comment #33
bircherThe followup for this is: #3079029: Move module config exclusion from config_environment to core.services
I am adding it (and also this issue) to the road map. #3033427: [plan] Add support for environment-specific configuration
Comment #35
larowlanCommitted 4e8f977 and pushed to 8.8.x. Thanks!
Published change record
🥇
Comment #37
claudiu.cristeaA related Drush issue https://github.com/drush-ops/drush/issues/4194
Comment #38
bircherUn-tagging the
8.8.0 highlights
since the related #3079029: Move module config exclusion from config_environment to core.services already has the tag. This issue here added the code to the alpha module, the other one made it stable which is the real highlight.