Git Workflow 2: Features Boogaloo
After the previous post about sticking to a Git workflow, questions came in about the elephant WAT duck in the version-control room: configuration management.
Since Drupal’s configuration and the content live together in the same database, deploying and tracking configuration changes can get messy. In Drupal 8, this will no longer be the case and all will be well. Unfortunately, that’s a bit of a ways off. If you’re using a framework other than Drupal, feel free to scroll to the comment form at the bottom and add your own tales of woe and/or triumph regarding the deployment and tracking of configuration settings.
My advice to fellow Drupalistas is a simple two-parter. First part: Use Features to get config into code and Git to get code where you want it. Second part: Don’t screw it up. This post details how to go about that second part.
Use Features for Deployment ...
I recommend "featurizing" configuration as much as you can. Whenever possible, I make extensive use of a combination of modules like Features, Strongarm, Features Extra, and other Features adjuncts as necessary, and then I add to the mix a lot of update hooks. It's a bit cumbersome and Features can be a bit of a beast, so I try to mitigate that by doing the following two three n things:
- Combine your field bases. When featurizing nodetypes, all field_base definitions should be combined into a single file. It’s tempting to roll a separate feature for each content type. I tried this once, but that caused conflicts wherever fields were shared across content types. These conflicts were hard to track down, because whichever one got enabled or reverted most recently would override the other content types’ field definitions. The result was myriad little discrepancies. Some things would appear overridden even after a features-revert-all. This can be avoided by combining all field_bases into one feature. Do this even if you don’t have fields shared by multiple content types, because it’ll save you having to refactor your features when you need to share fields someday.
- Limit the number of fingers in the pie. Each project needs a single release manager to administer the release plan. By the same token, it also needs a single Features manager to administer the featurization workflow. If multiple people are updating features and writing update hooks in the same sprint, that almost always leads to a headache.
- Avoid featurizing when you don’t have to. You can define, for example, a View in code and maintain it that way without even using Features. There are also small configuration items that can be managed with Features+Strongarm, but if those are the only thing you need Features for, then you might skip the headache and just run a bash script that does some variable_set()s via Drush. Features is a great big power tool but sometimes all you need is a little pair of tweezers. You don't have to use the power tool all the time. However, if you're already using it for something, it can also be convenient to put everything together.
This will be different for every project, and this is where you will use your own judgement no matter how detailed your prescribed workflow is. - Watch out! Remember that Features isn’t actually meant for deployment. I should probably feel deep shame for misusing Features so egregiously. But I don’t, because there isn’t a better option for deploying configuration without damaging content prior to Drupal 8, may its release be swift and glorious.
However this can result in strange behavior, and sometimes you have to hack your feature to avoid fun “gotchas.” Example: What do you suppose would happen if you featurize a view whose display is access-restricted by role on a site that has completely different roles? If you guessed “pick a role seemingly at random and grant access to the view to that role,” then you win a big gift basket full of WAT. The reason it does this is that roles get exported as raw role IDs. If your site has a user role called “super powerful adminstrator” with RID = 4 and you deploy to a site where “random user who just signed up” has RID = 4, then you have a potential problem when deploying features.Features generates something like this:
<br> $handler->display->display_options['access']['role'] = array(<br> 4 => ‘4’,<br> );<br>
But you can trick it into specifying role by name instead of ID like so:
<br>// Finds the role by name. If not found, then it’s nobody.<br>$super_powerful_rid = ($role = user_role_load_by_name(‘super powerful adminstrator’)) ? $role->rid : NULL;
// This is how Views sets access. Replace number with your role’s ID.
$handler->display->display_options['access']['role'] = array(
$super_powerful_rid => $super_powerful_rid ,
);So that if there is a role with the right name, you allow them access, and if not, you don’t allow access to anyone until someone goes in and sorts out the role mismatch. Much safer.
Rules does pretty much the same thing:
<br>"IF" : [<br> { "user_has_role" : { "account" : [ "account" ], "roles" : { "value" : { "4" : "4" } } } },<br>],<br>
Depending on what your rule does, this could be pretty disastrous. Imagine you execute a heavy query every time one of your super special admins views, which is fine. But now it’s running on every. single. pageview. Not good.
Once again, hacking your feature saves the day:
<br>// This part goes outside of the Rule declaration<br>$rid = ($role = user_role_load_by_name(super powerful administrator)) ? $role->rid : NULL;
…
"IF" : [
{ "user_has_role" : { "account" : [ "account" ], "roles" : { "value" : { "$rid" : "$rid" } } } },
],You can also use this to let users set configuration that is used by your feature without giving them access to the rule or view definition itself. Just stick a `variable_get()` in there, and revert the feature regularly (say, via a cron job), and views and rules can use your users’ configurable values relatively securely. Just don’t forget to escape your user inputs, kids.
… Even Though Features is Not Really Meant for Deployment
So, Wait. What Does This Have To Do With Git?
Well, there is one big danger to hacking your features. Since it’s not what Features is expecting, Features will steamroll right over your clever hacks with every `drush features-update`. Each update presents the risk of unexpectedly going back to whatever hardcoded values Features sees fit to include. Your best defense against this backslide is to review your staged changes verrry carefully.<br>git add my_features_stuff/<br>git diff --cached<br>
If Features does clobber your hacks, you’ll have to tiptoe through your code and cherry-pick the parts you want to keep. I find that Netbeans has a nice way of displaying changes and letting you click to revert a chunk to the unstaged version. Save and repeat until only the changes you want are staged. Then commit.
Congratulations! You did the wrong thing the right way, and it will work great, unless it doesn’t. In the latter case, I hope you followed all the previous advice about having a designated Features manager and making frequent, small commits. Otherwise, best of luck in that impending war with your past self.