Friday, May 1, 2015

More Automated Deployment

I'm still working on my automatic deployment system for the webcomic code. One thing that was left out of my first version was special handling for certain types of file updates.

For example, if I add a new package to package.json I'll need to run npm install after pulling the changes if I want the application to work. Fortunately, GitHub includes lists of files added, removed, and modified as part of the data POSTed to my listener.

If you want to see exactly what GitHub sends you from these Webhooks, just go to the management page for one of them. At the bottom, there's a list of all the POSTs they've sent (or attempted to send, in case some of them have failed). Click on the ids to expand the details and you'll see the JSON. I just copied and pasted it into JSON Editor Online so that I could easily examine it.

GitHub's Webhook transmission history - check it out!

If you look at them, you'll notice that the "head_commit" field has three fields named "added", "removed", and "modified." As you might expect, these fields are arrays of file names that were added, removed, or modified. I made a simple function to search through these arrays and look for package.json. If I find it, I know that I'll have to run the npm install command after the pull is complete.

function checkMods(listOfLists) {

    var ret = {
        runNpm: false,
        restartApp: false
    };

    for (var j = 0; j < listOfLists.length; j++) {
        var fileList = listOfLists[j];
        for (var i = 0; i < fileList.length; i++) {
            if (fileList[i] === "package.json") {
                ret.runNpm = true;
            }
        }
    }

    return ret;

}

My function returns an object that informs the caller if npm should be run and also includes a flag for telling the caller if the app should be restarted as well. I'm thinking ahead but haven't implemented that check quite yet.

I modified the "push" listener from earlier in the week to look like this now:

github.on("push", function(repo, ref, data) {

    var branchName = ref.substring(ref.lastIndexOf("/")+1);
    if (branchName === "master") {

        var repoName = data.repository.name;
        if (repoName === "comic") {

            var checks = checkMods([data.head_commit.added,
                                    data.head_commit.removed,
                                    data.head_commit.modified]);

            var toRun = "/home/comic/update_comic.sh";
            if (checks.runNpm) {
                toRun += " npm";
            }

            child = exec(toRun, function(error, stdout, stderr) {

                if (error !== null) {

                    var e = new Error("exec error: " + error);
                    console.log(e);

                } else {

                    console.log("pulled update for " + repoName);
                    if (checks.runNpm) {
                        console.log("...and installed new packages");
                    }

                }

            });

        }

    }

});

The only real change is the call to checkMods on line 9 that passes in an array of arrays, and the subsequent conditional addition of the "npm" flag on the Bash script that gets called. Obviously, that flag indicates to the script that npm install should be executed after git pull origin master.

Now I just need to get to work on that conditional restart part. In theory, I just have to look for changes to server-side .js files (the .js files not under public) and then flag the app for a restart. The biggest issue for me is my lack of Bash scripting skills. But I'll figure it out.

Soon, I'll have even more autonomous electronic robot mackerels in my sea.

If you're confused by that last statement, see today's comic:

Amphibian.com comic for 1 May 2015