Grunt Automation for WordPress developers

This is the third article in a series of developing for WordPress in a DevOps friendly way. The previous articles:

  1. Introduction to WordPress and DevOps
  2. Developing with WordPress and Vagrant
  3. Grunt Automation for WordPress developers
  4. WordPress configuration management

In the previous two posts we’ve had a look at the various tools we need to create an automated workflow for developing and a little bit about how Vagrant can be used to create a better development environment. We’ve also briefly touched on using Grunt and wp-cli to automate setting up WordPress inside the environment. In this post we’ll dive deeper into how to use Grunt to automate even more of the WordPress setup.

There’s a Git repository available with code for this post here: https://github.com/eriktorsner/gruntautomationforwp I suggest you clone or download the code to your development computer to make it easier to follow along.

What’s our goal?

In short. We want to run a single command at the command line to install WordPress, all our wanted plugins and other vital pieces of configuration. The end goal is to automate it to the extent that it can be part of a fully automated, non-interactive, deployment process.

Looking back at the first post in this series I talked a bit about DevOps and how it’s a bit of a challenge to make WordPress a good fit for it. Automation is one of the cornerstones within DevOps and historically WordPress has been more focused on getting up and running quick than on supporting an automated workflow.

Well, that changed entirely when wp-cli came onto the scene a few years back. Wp-cli stands for WordPress command line interface and is a project that aims to enable automation of WordPress management. Wp-cli makes it possible to automate installation, setup, theme and plugin installations and a lot of other common WordPress management tasks like modify settings or even add pages and posts.

The other part of the automation work will be carried out by Grunt. Grunt is a “modern JavaScript task runner” and allows us to create tasks that can be executed from the command line. So we can create a task “wp-install” that uses wp-cli to install WordPress and run that task from the command line using a simple “grunt wp-install”. The most common use case for Grunt is to run things like css and javascript minification or compile SASS file to css. But we’ll use Grunt mainly as a task runner for DevOps related tasks. That doesn’t stop you from using Grunt for those other things as well which may be a very good idea for your project.

Creating the environment

In the previous post, we covered how to get started with Vagrant. To follow along in this post I’m assuming that you’ve read that post and have installed the prerequisites for running Vagrant on your development computer. I’ve made a separate Github repository available for this post, so from now on when I’m talking about specific files, I’m referring to the ones you can find in the git repo at https://github.com/eriktorsner/gruntautomationforwp. If you have any problems following these steps, be sure to read up on the previous post and post any questions in the comments below if you’re still having issues.

A quick checklist to get you started with this Vagrant box

  1. Edit Vagrantfile (hostname, dns names and ip-numner) to fit in your environment
  2. copy localsettings.json.template to localsettings.json
  3. Run “vagrant up” to initialize the new machine”
  4. Go to folder /vagrant inside the virtual machine and run “npm install”

Wp-cli + Grunt

To show what Wp-cli can do in terms of automation, we’ll use it to perform the following tasks:

  1. Install WordPress
  2. Install and activate a few plugins from the official WordPress repo
  3. Change a few settings

As we’ll be doing this by automating wp-cli via Grunt. Let’s have a look at the important parts of the Grunt file that comes with this post (see the top of the post for information on how to get it). The first thing to notice is on 32 where we define the function getLocalSettings, a simple helper function.

[code firstline=”53″ title=”getLocalsettings” language=”js”]

function getLocalsettings(test) {
ls = grunt.file.readJSON(‘localsettings.json’);
if(ls.wppath === undefined) ls.wppath = shell.pwd() + ‘/www/wordpress-default’;
return ls;
}

[/code]

We’ll use this function in each of our Grunt tasks that deals with wp-cli so that we can get the parameters we define in localsettings.json into action. Let’s look at the settings file to get a hunch of what kind of parameters we’re including

[code title=”localsettings.json” language=”js”]

{
"environment": "development",
"url": "www.gruntautomationforwp.local",
"dbhost": "localhost",
"dbname": "wordpress",
"dbuser": "wordpress",
"dbpass": "wordpress",
"wpuser": "admin",
"wppass": "admin",
"wppath": "/vagrant/www/wordpress-default"
}

[/code]

 

The idea is to have anything that is specific for this specific machine should be a parameter in this file. And the point of having settings in a json format is that json can be easily accessed both from javascript code like in the Gruntfile and in PHP code as well.

Let’s look at the first task defined, the default task:

[code title=”Grunt default task” language=”js”]

grunt.registerTask(‘default’, ‘Log some stuff.’, function() {
ls = getLocalsettings();
grunt.log.write(‘localsettings successfully read URL=’ + ls.url).ok();
});

[/code]

It will simply read the settings file and output the URL parameter from it, so it’s a simple test. Let’s try it within the Vagrant machine:

[code language=”shell”]
$ cd /vagrant
$ grunt
Running "default" task
localsettings successfully read URL=www.gruntautomationforwp.localOK

Done, without errors.
[/code]

The next task to look at is the wp-install task, it’s a bit more interesting:

[code title=”Gruntfile wp-install” firstline=”14″ language=”js”]
/*
* Installing WP
*/
grunt.registerTask(‘wp-install’, ”, function() {
ls = getLocalsettings();
wpcmd = ‘wp –path=’ + ls.wppath + ‘ –allow-root ‘;

shell.mkdir(‘-p’, ls.wppath);

if(!shell.test(‘-e’, ls.wppath + ‘/wp-config.php’)) {
shell.exec(wpcmd + ‘core download –force’);
shell.exec(wpcmd + ‘core config –dbname=’ + ls.dbname + ‘ — dbuser=’ + ls.dbuser + ‘ –dbpass=’ + ls.dbpass + ‘ –quiet’);
shell.exec(wpcmd + ‘core install –url=’ + ls.url + ‘ –title="WordPress App" –admin_name=’ + ls.wpuser + ‘ –admin_email="admin@local.dev" –admin_password="’ + ls.wppass + ‘"’);
} else {
grunt.log.write(‘WordPress is already installed’).ok();
}
});
[/code]

There are a few interesting things to note. The first is obviously that after running this task we’re going to have a fully functioning WordPress installation in a sub folder. All parameters we needed are kept in a separate file which is fundamentally good for our efforts to make this a fully automated WordPress install. We’ve also managed to install WordPress in a sub folder that is explicitly kept out of source code control.

The reason for that is that WordPress in not our code, it’s a depencency to us. We want to avoid adding dependencies in our source code tree (look inside .gitignore, we’re explicitly ignoring the www sub folder) for several reasons. But the most important reason is not size or bloat, it’s the ability to handle upgrades well. WordPress and it’s extensions are upgraded frequently with major and minor upgrades. If you add all the source code for WordPress, themes and plugins into your own source code tree, you will be stuck with the task of upgrading them once new versions arrive, and they do quite often.

A clean separation between your own code and the dependencies means that you can easily test your code with the newest versions of each dependency. If your code breaks as a result of upgrading one of the third party plugins, the clean separation and automated setup makes it super easy to test and fix any such problems before you upgrade anything in production. That’s a big deal.

So, back to our example, let’s run the wp-install task:

[code language=”shell”]
$ grunt wp-install
Running "wp-install" task
Downloading WordPress 4.3.1 (en_US)…
Success: WordPress downloaded.
sh: 1: /usr/sbin/sendmail: not found
Success: WordPress installed successfully.
[/code]

Adding more dependencies

Obviously, we’re going to need more dependencies. Let’s add a few plugins from the WordPress plugin repository. This is done with the wp-cli command “plugin install”. Let’s look at the next task in Gruntfile.js:

[code title=”Gruntfile wp-install” firstline=”32″ language=”js”]
/*
* Setting up WP
*
*/
grunt.registerTask(‘wp-setup’, ”, function() {
ls = getLocalsettings();
wpcmd = ‘wp –path=’ + ls.wppath + ‘ –allow-root ‘;

// some standard plugins
stdplugins = [‘if-menu’, ‘baw-login-logout-menu’,’google-analyticator’];
for(i=0;i<stdplugins.length;i++) {
name = stdplugins[i];
shell.exec(wpcmd + ‘plugin install –activate ‘ + name);
}
})
[/code]

So no more manual plugin installations in the WordPress admin area! Let’s run the Grunt wp-setup task:

[code language=”shell”]
$ grunt wp-setup
Running "wp-setup" task
Installing If Menu (0.3)
Downloading install package from https://downloads.wordpress.org/plugin/if-menu.zip…
Unpacking the package…
Installing the plugin…
Plugin installed successfully.
Success: Translations updates are not needed for the ‘English (US)’ locale.
Activating ‘if-menu’…
Success: Plugin ‘if-menu’ activated.
Installing Login Logout Menu (1.3.3)
Downloading install package from https://downloads.wordpress.org/plugin/baw-login-logout-menu.zip…
Unpacking the package…
Installing the plugin…
Plugin installed successfully.
Success: Translations updates are not needed for the ‘English (US)’ locale.
Activating ‘baw-login-logout-menu’…
Success: Plugin ‘baw-login-logout-menu’ activated.
Installing Google Analyticator (6.4.9.6)
Downloading install package from https://downloads.wordpress.org/plugin/google-analyticator.6.4.9.6.zip…
Unpacking the package…
Installing the plugin…
Plugin installed successfully.
Success: Translations updates are not needed for the ‘English (US)’ locale.
Activating ‘google-analyticator’…
Success: Plugin ‘google-analyticator’ activated.

Done, without errors.

[/code]

Working with settings

The last part I’m going to cover in this post is a little bit on how to manipulate WordPress settings. Wp-cli is not the only option we have to get settings from a text file (code) into a live WordPress installation, but since wp-cli can do this, let’s have a look at how.

Most settings in WordPress are stored in a table named wp_options (assuming you’ve kept the standard “wp_” table name prefix). In order to change any of those settings via wp-cli, we first need to figure out what the various settings are called. Wp-cli have a sub command “option” that lets us list, add, delete or modify settings. For instance, if we want to change the settings “Site title” and “Tagline” we could use wp-cli to list all options and search for the options we want to find. In the wp-install task, we set the string “WordPress App” as the title, lets search for it:

[code language=”shell”]
$ cd /vagrant/www/wordpress-default/
$ wp option list | grep ‘WordPress App’
blogname WordPress App
[/code]

Ok, there’s only one single row in the options table with that string, so we can safely assume that the option called “blogname” corresponds to Site title. Let’s look for the tagline, we haven’t changed it yet in our test installation, so it should still read “Just another WordPress site”:

[code language=”shell”]
$ wp option list | grep ‘ust another WordPress site’
blogdescription Just another WordPress site
[/code]

Right. The parameter tagline that we can control in the WordPress admin is found in the option named “blogdescription”.

Knowing what the options are called makes it possible to set them via wp-cli. We can add a few lines to the wp-setup task:

[code language=”shell”]
shell.exec(wpcmd + ‘option update blogname "Another title"’);
shell.exec(wpcmd + ‘option update blogdescription "experimental tagline");
[/code]

Running the wp-setup task again, we should now see:

[code language=”shell”]
$ grunt wp-setup
Running "wp-setup" task
Warning: if-menu: Plugin already installed.
Activating ‘if-menu’…
Warning: Plugin ‘if-menu’ is already active.
Warning: baw-login-logout-menu: Plugin already installed.
Activating ‘baw-login-logout-menu’…
Warning: Plugin ‘baw-login-logout-menu’ is already active.
Warning: google-analyticator: Plugin already installed.
Activating ‘google-analyticator’…
Warning: Plugin ‘google-analyticator’ is already active.
Success: Updated ‘blogname’ option.
Success: Updated ‘blogdescription’ option.

Done, without errors.
[/code]

Note that wp-cli won’t attempt to reinstall the plugins that are already activated but instead just sends a warning saying that it’s already there. The last few lines tells us that the blogname and blogdescription settings are now updated to their new values. Mission accomplished.

 

Summary

In this post, we’ve taken another few steps towards gettings WordPress under our control. We’ve discussed the reasons why we want to treat WordPress itself as well as any third party extensions as dependencies rather than a part of our own application. Not in the sense that the code we develop would work well without WordPress and the plugins, but because we need to keep them out of our code base for upgrading and testing reasons.

In future installments in this series, we’ll dive into dealing with content and try to find ways to make WordPress separate content and content. If that sounds weird or if you’re just the curious anyway, stay tuned for the next episode. In the mean time, share insights, questions or other feedback in the comments below.

WordPress DevOps – The book

I’ve written an ebook on this subject. Released in September this year on Leanpub.com. Expect a 100+ tightly written pages where we walk through the creation of the skeleton of a WordPress based Saas application, connected to Stripe and Paypal with a working deployment process that takes content into account. Just add your billion dollar idea. Jump on over to Leanpub to get your copy.

WordPress DevOps - Strategies for developing and deploying with WordPress
WordPress DevOps – Strategies for developing and deploying with WordPress

 

 

 

 

 

 

 

[wysija_form id=”3″]

Leave a comment

Your email address will not be published. Required fields are marked *