Problem/Motivation
In some cases, contrib and core modules provide functionality that is driven by config that for some use cases might be considered as content or - in general - you want to allow CRUD operations. For instances Yaml Forms / Webforms, Page Manager instances, Menus, etc.
Currently Config Readonly allows a boolean choice: can or cannot change any configuration.
Proposed resolution
Provide a hook_config_readonly_whitelist_patterns() where developers can define whitelisted configuration patterns for which CRUD operations are allowed even when Config Readonly is switched ON.
/**
* Implements hook_config_readonly_whitelist_patterns().
*/
function MODULENAME_config_readonly_whitelist_patterns() {
return [
'*article2*',
'system.performance',
];
}
Original Issue summary
First off, thanks for an excellent module; helps keep merge conflicts out of production, I'm sure it has saved us many headaches.
In some cases, modules and core provide functionality that is driven by config that for some use cases might be considered as content. For instance, at the company I work for, we like to provide our clients with a Form Builder so we give them the powerful YAML Form module. At the moment, we use a module that extends your module to add exceptions and use a combination of config import --partial and great care.
Since then I've been pointed to drush cexy and cimy (https://www.previousnext.com.au/blog/introducing-drush-cmi-tools) which allow certain config to be ignored when you make a decision for a site to effectively consider them instead as content.
If you are open to it, I would love to submit a patch that does two things:
- allow the administrator to add exceptions (potentially with wildcards)
- allow the administrator to additionally decide to have exceptions loaded from the ./drush/config-ignore.yml used by drush cexy/cimy
Would that be something you'd be willing to consider?
| Comment | File | Size | Author |
|---|---|---|---|
| #84 | 2826274-84.patch | 16.9 KB | manuel garcia |
| #84 | interdiff-2826274-79-84.txt | 937 bytes | manuel garcia |
| #79 | 2826274-79.patch | 16.9 KB | manuel garcia |
| #79 | interdiff-2826274-78-79.txt | 531 bytes | manuel garcia |
| #78 | 2826274-78.patch | 16.9 KB | manuel garcia |
Comments
Comment #2
micbar commentedMaybe this is related to your proposal.
I would like to unlock the /admin/structure/menu/manage/main form to allow users to drag menu items around and change the order.
I wonder if the weight of a menu link is content not config.
Comment #3
scott_euser commentedYes, that is an example where for your site you'd like to make an exception. The proposed patch I would write would allow that.
Comment #4
micbar commentedI am interested in your patch! I had to disable this module.
Comment #5
scott_euser commentedThis is a pretty hefty patch as it adds a lot of functionality, but I hope others also find it very useful.
You can:
It works for both drush/drupal console blocking as well as the admin / cms area blocking.
Comment #6
scott_euser commentedComment #8
scott_euser commentedRe-run with config install and config setup (although a bit unsure about this!)
Comment #9
scott_euser commentedComment #11
scott_euser commentedComment #12
scott_euser commentedComment #14
scott_euser commentedFix parse error
Comment #15
moshe weitzman commentedI think this makes sense from a feature point of view. I didnt code review, as patch looks a bit large.
Comment #16
scott_euser commentedThanks for the comment and support for the feature moshe weitzman.
Yes it is a big patch, it offers a very significant amount of new functionality. To help a code reviewer, the files modified / added cover the following:
As well, it provides the necessary tweaks / additions to the schema, tests, etc to handle the above.
I have made a couple naming modifications in the attached patch that will hopefully make it more clear + added a new permission so who can control the exceptions can be controlled.
Comment #17
pwolanin commentedCalling it "Exceptions" is very confusing. It's a whitelist as asked for here: #2851286: Consider a whitelist to allow "some" configuration to be edited We need to rename all the things in the patch.
This patch is also too big and complex. I could possibly support a white list of config names in a simple config object. Having a service is confusing and overkill I think.
The whitelist config object should never be able to be modified in production.
Comment #18
pwolanin commentedComment #19
scott_euser commentedThanks for the review, all makes sense. I will get batch to you with a much simplified patch offering the basics of the whitelist functionality.
Comment #20
scott_euser commentedI have greatly simplified this now:
Where I got stuck with the test (it will fail):
Bit of a restraint on the whitelist is it can't apply to ConfigFormBase because getEditableConfigNames is a protected function.
Comment #21
scott_euser commentedComment #22
gambryComment starts with uppercase C and H.
Are we happy to leave "exceptions" in here?
Same as above.
Node module doesn't create any content type, the "standard" profile does. You'll need to use
drupalCreateContentType(). I get some fatal errors adding this myself to the test, but probably it's just my instance.Comment #23
gambryAfter further tests:
$name variable doesn't exist in this context.
Name space should be Drupal\config_readonly\Exception
ConfigReadonlyWhitelistException class can't be found without the
usestatement.Comment #24
scott_euser commentedThanks for reviewing it @gambry, much appreciated!
Updated attached, getting there with the test, but the Trait is unable to read the file; I need to spend some more time to figure out how to write a file during a test as the file is readable from the test but not from the Trait. Will try to work on it further again later.
Comment #25
gambryApparently you can't use
$this->writeSettings()twice in the same test, even with$this->resetAll(). Not sure if this is expect or a bug in core test framework.Splitting in 2 tests works.
Besides I would personally separate the WhiteList tests in a separated class. In this way the 'node' dependency is not loaded when not needed (i.e for original config_readonly tests). But that is probably me being too picky.
ConfigReadonlyWhitelistTraitdoesn't take in consideration the case when the setting "config_readonly_whitelist" is there but no file is provided ($whitelist is an empty string) and the case when the file is empty (returned $pattern is undefined).This is an interesting and useful feature, really looking forward for using it!
Comment #26
scott_euser commentedNice one for solving that, was banging my head against the wall a bit wondering why it was failing; moved into a separate test file and worked fine!
I updated the trait to add that condition as well as set the default patterns empty array and added comments to make the method more clear.
Comment #27
scott_euser commentedCleaned up unused method from test.
Comment #30
gambryTest fail due modules form being changed. I can't find exactly the commit, but I can see from the interface
modules[Core][action][enable]should be nowmodules[action][enable]. That should probably be fixed before any other progress can be made here.Comment #31
gambryI've created #2851929: ReadOnlyConfigTest fails when using core 8.3.x or 8.4.x branches and submitted a patch. Until that is merged in we can temporary test against 8.2.x, as that should pass.
Patch at #27 looks good to me, if test is clean I'm assuming this can be RTBC.
Comment #32
pwolanin commentedI'm not sure I understand the motivation of using an external file as a whitelist source.
I think the whitelist itself should be a config object that cannot be changed while config is locked. Then you could track that together with other config changes?
Comment #33
scott_euser commentedThanks @gambry for all the help and spotting that!
@pwolanin Essentially the idea is to keep it compatible with the drush cimy/cexy commands from PreviousNext which is being a more and more common workflow addressing a different part of the same problem this is trying to solve.
Easiest example case I can think:
How the combination of the two tools solves this:
Some more info on drush cimy/cexy:
Comment #34
scott_euser commentedHold that thought, on considering this further, perhaps the two (config object & drush cimy/cexy compatibility) are not mutually exclusive and can be compatible with each other; I will revisit and see if it works; will try to get back to you in the next couple of days.
Comment #35
scott_euser commentedUpdated to use config object via new form extending ConfigFormBase.
Confirmed that this is compatible with drush cimy/cexy so long as we use the key 'ignore' which deviates slightly from whitelist, but hopefully not a breaker (would like to avoid maintaining 2 whitelists, one for each tool).
Updated the test accordingly. The main patch will fail as per #31, so submitted a second discardable patch with that fix incorporated to show test pass.
Comment #37
bircherHello, please allow me to chime in.
Adding a text file is in my opinion not a good strategy.
configuring it via a configuration object can make sense, but to interact with it from another module one would have to start overriding configuration just for that.
What I had in mind was a service collector or a plugin, or at the very least a hook.
A hook seems to me to be the simplest form for now as you can always create a small module that implements that hook and reads from a text file, or from configuration.
Allow me to explain where this comes from: I maintain config_split, which is in my humble opinion a very elegant solution to prevent configuration of clients to be overwritten and to maintain development modules and config only in that environment. I am planning also to make it extensible so that other modules such as config_ignore could benefit from the same approach. It works with the basic drush commands as well (or it will as soon as I apply the patch in #2839452)
My plan is to make config_split and possibly config_ignore work with config_readonly.
Comment #38
bircherPS: I also incorporated 2851929-2 and I added the whitelist for simple configuration forms.
Comment #41
scott_euser commentedThanks for working together on this @bircher, happy to see you involved! I am very keen to get this in in some form as these various modules could benefit from being combined with config read-only. I will try to test and review your patch before the end of the week.
For those interested in a comparison of where there is overlap (eg, drush cimy/cexy has a lot of overlap with config_split) @adamzimmerman has done a write up https://chromatichq.com/blog/managing-complex-configuration-drupal-8
Comment #42
scott_euser commentedLooks solid to me; nice one solving the ConfigFormBase->getEditableConfigNames()!
I have attempted to make a simple module to bridge Drush CMI Tools and Config Read-only to test your patch and it was of course easy, so I am happy with this solution and I think it fits with #17 desire to keep it simple + is more flexible this way to work with other config related modules.
Just two minor tweaks:
For 2) added the config name in as a variable as per the exception docs as I found it difficult to debug without it. For instance, if you create a node type 'article' and you don't have a very liberal whitelist like '*article*' but instead something more specific like 'node.type.article' and 'article' the form does not get locked. Then when saving the form, the node module automatically attempts to adds a body field. Without the exception like this, it would be difficult to find out that that is why it was thrown.
Comment #43
jamesdeee commentedComment #44
jamesdeee commentedComment #45
scott_euser commentedUpdated patch to match current version of dev brench. The code is the same, but where it sits relatively to the existing code has changed too much that interdiff doesn't seem to be able to handle it.
Comment #46
jamesdeee commentedWhat version of Drupal & Config Readonly are you doing this with, Scott? I'm still having a hard time getting the patch to apply.
Comment #47
scott_euser commentedThis is what I'm doing to apply the patch:
Let me know if that doesn't work for you.
Comment #48
gambryComment #49
gaëlgI wanted to allow users to reorder menu links (/admin/structure/menu/manage/main). I applied the patch and tried to whitelist 'system.menu.*' in the hook but it did not work.
After looking at the code, I figured out that the form is a config entity form, so I have to put the name of the entity in the whitelist: 'main'. But it seems to me like it's too wide and could lead to false positives because there is no "namespace". Some other config entity could also be named 'main', no?
Comment #50
gaëlgActually, replacing
$name = $entity->getConfigTarget();with$name = $entity->getConfigDependencyName();inReadOnlyFormSubscriber.php(at "// Don't block particular patterns.") solves the above problem. I can usesystem.menu.*in the whitelist.But: I still have an error after reordering menu links when saving my view, because some menu links are config and not content (for example, a page view whose menu link is configured in the view configuration page). So I would have to whitelist all of these links, which implies allowing too much things (in the previous example, it would allow to edit the view although config readonly is activated).
I'm wondering if the problem of menu items reordering can be handled with this whitelist system. This might need another issue queue.
Edit: and here's the issue queue: #2892631: Don't block content menu links reordering
Comment #51
gaëlgHere's an updated patch to fix #49 by doing what I said in #50.
Comment #52
scott_euser commentedThanks for the updated patch - confirmed this latest patch continues to work for my use cases.
Comment #53
daniel wentsch commentedThanks for your efforts finding a solution to whitelisting certai configurations. What's not clear for me:
With the latest patch applied against dev – how would I define my whitelist?
Comment #54
scott_euser commentedYou can do this to implement the whitelist with the latest patch:
Comment #55
gambryThis is a really good solution. I think will need a future revisions, as per #37 and Config Filter, but this is more a overall Config Readonly roadmap and so to be discussed with maintainer(s) and generally out of the scope for this issue.
I've also updated the IS to reflect latest changes, as current version still mentions the yaml text file in the codebase for whitelisting.
Comment #56
vijaycs85I have created Config Readonly Filter to solve this very same problem. As these configurations could be site/environment specific, having them as config entity and export with default configuration would be better. Probably we could add alter/hook to the discovery as well in future.
Comment #57
landsman commented@vijaycs85 I think that this filter module should be in part of the main module.
What do you think? Is possible to merge it?
Comment #58
vijaycs85@landsman sure, if we could add.
Comment #59
douggreen commentedNew patch attached:
I've tried to do minimal work on this patch. I did not address the following in my new patch:
@vijaycs85's solution is just another solution. I have not reviewed it. But as another contrib module solution, it shouldn't derail the work here.
Marking back to needs review for my most recent changes.
Comment #61
manuel garcia commentedRerolled #59 which would not apply, by applying #51 and then the interdiff #59 on top of it.
I had a look at the changes, my comments:
1. I think adding the settings.php option is a good addition as it opens up the possibility of defining these settings per environment.
2. The refactor of the "hack" is a great idea.
Comment #62
manuel garcia commentedI tested the following:
With this on my
settings.php:I was able to edit any menu on the system, tested:
This all worked flawlessly, so switching back to RTBC.
Comment #63
pwolanin commentedin general, someone else should review if you wrote the last patch
Comment #64
pwolanin commentedA few nits before this is done.
A bunch of
@throws ConfigReadonlyStorageExceptionshould be fixed to use the full class name of the exception.A place or two missing @throws, and some other doc comments.
Usually use fully namespaced name here
This is a config name, not a lock name?
does this needs an @throws?
The new exception:
class ConfigReadonlyStorageException extends \Exception {}should perhaps extend something a little more specific like \RuntimeException, and have a \n in the braces like core?
Comment #65
manuel garcia commentedAddressing #64 - thanks @pwolanin for the review
Comment #66
douggreen commentedA few more minor comments. (Sorry I didn't notice these before. The first one is the only really important one.)
Add a local variable for protected $moduleHandler.
I'd make this an elseif instead of an if. It is mutually exclusive from the previous conditional.
Comment #67
manuel garcia commentedAddressing #66.
Comment #68
douggreen commentedThanks @manuel-garcia!
Comment #70
manuel garcia commentedComment #72
douggreen commented@manuel-garcia, this is confusing that $moduleHandler is declared in the trait, but set in the class. Can we refactor so that it is defined and set in the same place? I suggest that we add a setter function to the trait, and call the trait setter function from the class constructor. That way the variable that the trait uses is self contained in the trait.
Comment #73
manuel garcia commentedThe change to an elseif suggested in #66.2 is invalid. Reason being that the if the first if block runs, then
$mark_form_read_onlyis assigned again.Comment #74
manuel garcia commentedRe #72 I think that would be a strange pattern to implement. On the latest patch the module handler is not declared on the trait although it makes use of it on
getWhitelistPatterns().$moduleHandleris declared on the two classes that do use the trait (ReadOnlyFormSubscriberandConfigReadonlyStorage).I also tried a couple of other things:
ConfigReadonlyWhitelistTrait::getWhitelistPatterns, and have the trait just holdmatchesWhitelistPatternaccepting a name and array of patterns. To me this seems like the cleaner trait, yet it means we having to get the patterns on several places instead of one.So I am leaving the patch as is right now, perhaps the maintainers can weight in on this, I attach the interdiff of what implementing #72 would look like just in case.
Comment #75
pwolanin commentedLooks at examples in core some do seem to have setters or methods to interact with the variables defined by the trait.
see e.g. \Drupal\Core\Entity\DependencyTrait
in other cases they access the trait variable directly like
\Drupal\Core\Database\Query\InsertTrait
The variable in this patch is a service, which isn't something I've found used yet in core. I think I'd lean towards defining the property in the trait since it's used there. Or possibly requiring it to be passed into the trait;s method calls, but that seems like unneeded complexity.
A question, why is this method even public? seems to be used only internally to the service?
Comment #76
manuel garcia commentedThanks @pwolanin for weighting in on this, adding a setter for the module handler to the trait here.
Comment #78
manuel garcia commentedOops.
Comment #79
manuel garcia commentedRe #75 I couldnt find a reason not to, so declaring
matchesWhitelistPatternas protected here.Comment #80
douggreen commentedComment #81
douggreen commentedI just ran into an error when deploying one of our sites with this patch. I'm not sure yet the cause of the error, so I'm removing RTBC until I track it down. It might just be that we need to do a cache_clear before this runs, because of the new services, but let me track it down before anyone commits it.
Comment #82
douggreen commentedMarking this as RTBC now, we figured out that our builds need an additional 'drush cr' in our deploy scripts.
Comment #83
pwolanin commentedThe use of the reflection class is a bit icky - is #2095289: Make getEditableConfigNames() public and use it in config_translation stalled? Looks like alexpott was willing to consider 8.x solutions.
Still a bit unsure why setModuleHandler() and getWhitelistPatterns() should be public (api additions)
Comment #84
manuel garcia commentedComment #85
douggreen commented@pwolanin Yes the reflection class is icky. We have no other possible solution in 8.x. Changing it is an API break. I tried to document this in the code as cleanly as possible by giving it it's own method, and by adding a @todo that says this can't be fixed in 8.x.
Comment #86
douggreen commentedMarking back to RTBC after testing @manuel-appnovation's changes. We still have the reflection. The best we can do is document this. I think it's documented sufficiently in the code, and that documentation has been reviewed here by a couple people. It's up to @pwolanin to accept or suggest how better to document it.
Comment #88
pwolanin commentedComment #89
scott_euser commentedExcellent, thank you!
Comment #91
Prabhu.shan commentedHi,
This is amazing module. Thanks everyone for the contribution. I would like to allow editing to admin --> people --> Permission and also for roles.Currently this modules locks down all the pages in configuration and also core modules - like installing modules.
Comment #92
elijah lynnFor those looking for documentation on this new feature it is here https://cgit.drupalcode.org/config_readonly/tree/README.txt