Continuous Integration and Deploying with Github Webhooks

CI/CD

(Note:- The post was migrated from the previous blog written on 17th March 2018 web.arvhive.org). That was a lossy migration and the images were not be able to recover using webarchive. See What Happend to the previous blog

Last few days I spent digging into Continuous integration and delivering. I also have published a post about CI with Travis CI before. This time I wanted to run my own CI system. Why? Well that Idea came to me when we were developing a project in university. We had to develop an app to collect voice samples in Sinhala language from the public for the project and we developed a simple responsive mobile app for that. You can find it here.(yeah it is a simple app, why the hack you made it https ? it a simple app!! :D unfortunately we had to get an SSL to use microphone via the browser otherwise browsers won’t let us to use the mic. I used my free student SSL provided by Namecheap under Github Student Pack Promotion). I wrote the back end and it had only few functionalities (saving a voice sample on the Yandex drive and the local server) and they had been well tested. The front-end made the trouble here (yes yes front ends are trouble makers 😀 ). It was a react app. We developed the application while it was running on the server. While we were doing QA we had to changes every time. whenever we found a new bug/improvement we needed to,

  1. Commit the change to Github.
  2. Log on to the server using Putty.
  3. Change the current directory.
  4. Pull the repository.
  5. Build the react build
  6. Restart the back end

Doing this for a simple typo was really annoying. :/ So I wanted to AUTOMATE it! While I was reading about Travis CI and Ansible (a Deployment automation tool) I realized the important of Webhooks. Basically it is an HTTP callback which notify to an endpoint when an event occurs. Github also has this facility.

For the web app we all contributed was not owned by me. So I had to use one of my own repo. I used a react application I’ve hosted on Github called youtubeDownloader ( It is a simple application which is used to save youtube video files in Mega clould– this give free 50GB storage with a super encryption facility). For the testing I used my droplet on DigitalOcean which I bought using free credit that I got fromGithub Student Pack Promotion. My server is a Ubuntu 16.04.3 having 1GB RAM. Back to the topic. Here is overall architecture.

The started configuring the webhook first. You can configure this in Settings à Webhook à Add Webhook .(password confirmation step will be added here.

Then you will have to configure the following settings.

Payload URL – This is the endpoint of your listening server. My one was in following format – http://myip:8081/api/newPull

Content type – This sets the format of the content you are receiving. This can be either application/json or application/x-www-form-urlencoded. I personally prefer application/json as it is really easy to access.

Secret – This a really important and I will explain the important with the implementation. Just think this is some kind of an encryption key. Basically all the data will be encrypted using the key.

The I chose “just the push event” as I only needed to automate things when a push happens. Also do not forget to tick the “Active” checkbox.

Now we have to configure the server to listen for event notifications and the entered end point in the server.

Here is the server. It is just a simple Nodejs server running on ubuntu. The following endpoint is the one that the pull request is sent by Github.

router.post('/newPull',function(req,res){
....
}

Now the basics of cryptography comes in to the play. Remember the “secret” that you entered on Github repo? The requset data comes from Github is encrypted by the using HMAC . Github takes SHA1 hash function and the “Secret” you entered and the response body data to generate HMAC digest.This encryption helps to protect data integrity and the verify authentication. Github sends the HMAC they calculated as the field “X-Hub-Signature” inside the post request header. We must implement a way to regenerated the HMAC signature from our side too using post request body, secret key and sha1 hash function. If those to matches we can verify the request.

I created .env file containing the session secret as follows. Then added .env to gitignore file to stop it is committed to github repository.

SECRET_TOKEN=myToken
}

Installing ‘dotenv’ node module helps to import environment variables in nodeJs. We just have to import it as,

require('dotenv').config()
}

We can access them as

process.env.SECRET_TOKEN
}

This is how we can implement HMAC generation from our side.


router.post('/newPull',function(req,res){

  var hmac, signature;
  //configuring hash function using sha1 and secret. 
  hmac = crypto.createHmac("sha1", process.env.SECRET_TOKEN);
  //generating hash using request body 
  hmac.update(JSON.stringify(req.body));
  // format it to github format
  signature ="sha1="+hmac.digest("hex");
  //validating 
  if(signature===req.headers['x-hub-signature']){
    //both the signatures match continue CI/CD
    CI.builder(res);
  }
  else{
    res.sendStatus(200);    
  }
});

}

The we can run our server and test configuration. You can see a “Recent Delivery” section under your webhook on Github. This tells whether your end point responded to the webhook post request or not. If yes you can see a 200 -ok response with green colour. If there is something wrong it will get red.

You can use “Redeliver” to deliver the payload again for testing.

(Please consider the time out of the github request is low. So you have to take care of the responding to github request before running your server CI scripts. Otherwise logs will be in red.)

Now the notifying part is properly configured so we have to focus on automation ♥♥♥. Here is the script file that does the automation.

git -C ../youtubeDownloader/react/ pull origin master

sudo npm run build --prefix ../youtubeDownloader/react/
}

The first line pulling the github repository. But why – C ? We are runing this script while not being in the relevant git directory. So we have to point git that this is the directory that should get updated and check the .git directory in the path mentioned. Also it is essential to save your github credentials to run the aboue. Otherwise a authentication step may be added. It makes automation harder.

git config credential.helper store
}

The second one also does something similar. It points to the directory path to npm using –prefix flag. “npm run build” is the script that we use to build react build directory. sudo may need to use to avoid permission issues.

after creating the above script file as “autoBuilder.sh” inside the server. Now we have to give sufficient permimissions

chmod +x autoBuilder.sh
}

Now we have a method to be notified and a script to automate. Now what. well a little linkage left. But how? If I simply say we need to find a way to run a shell script when a new request comes from Github. We can use ShellJs package in npm for that. It allowed us to run terminal command using Node. ShellJs API allows to run basic shell commands like cp, cd etc. Also we can execute a shell command like,

shell.exec("sudo ./autoBuilder.sh");
}

I developed a separate controller called “CI.js” for running that. Here is it.

var shell = require('shelljs');

exports.builder=function(res){
    shell.echo('this is from shelljs Module');
    //sending the response for github post request as it gets time out if we wait until automation.
    res.sendStatus(200);
    shell.exec("sudo ./autoBuilder.sh"); 

}
}

(I have placed my “autoBuilder.sh” inside the listening server directory root).

This is how my console seems when a I added a new “readme.md” file to the master.

Few More things.

In you use

node server.js

or

npm start

commands to start the node server of the application. It won’t adapt to the changes happen in the build file. So we have to take care of that too. We can use nodemon npm package to listen for the file changes on the server. It can be simply installed as follows.

npm install -g nodemon

First change the current directory to the server directory of the application. Then start a new window using screen. (Normally we use “screen” tool to start services on the server. it lets us to keep running the service while we doing something else on the server. here is a guideline from digitalOcean for screen tool) .

screen
nodemon server.js

Now press to leave the screen.

CTRL + a + d

What’s More?

This seems easy and simple. But there are few drawbacks with this approach. Yet we can address them efficiently.

The build that we deploy on the live environment is not properly tested.

  • Building the build directory only on a test environment when a commit comes.
  • Testing the build using “npm test”. (we can write scripts for testing under “test” script in package.json)
  • If it passes put deploy it to the production(simply coping), else reject. The server restarts each and every time when we are committing, even for minor commits! We can implement a method to filter out the commits notification and build for only specific commits.

We don’t manages the releases here. That is really bad practise to do if we are deploy something serious. Deployment automation tools like Ansible, Chef could help for these problems.

That’s it for this post. Hope you learned something out of this. Thanks for reading.

Avatar
Sudeepa Nadeeshan
Research Assistant

My research interests include Intelligent Transport Systems, Machine Learning.