Sunday, September 28, 2014

CI: When GitHub/Travis meet Go/GAE

photo taken from Eric Peterson's presentation

A Word About Continuous Integration

When developing systems that work together, some of them dependant on each other, most of them are deployed to cloud infrastructures, some of them self hosted, some of them are internal tools that prepare stuff, some are automated testing suites (using a selenium could perhaps) - reaching CI is a critical part of a viable development model. Without it, we would spend days and months worrying about boring integration tasks, writing hundreds of scripts and it would significantly cripple our ability to write good code fast. When a problem occurs,  without CI, figuring out where in the chain something went wrong might be very difficult and expensive.

Why We Like GitHub and Travis CI

It is important for us to streamline our development process with the familiar process great developers are used to, and this is why we chose working with GitHub and Travis CI, and not, say, GitLab and Jenkins. The culture where every workplace in the industry uses their own thing is something we think can be improved. If you are hiring good developers, chances are they already have GitHub repos, and they know the git flow branching model in some form or another. If you are hiring really good developers, chances are they also have their own open source repos connected to Travis CI on GitHub for free. Your developers already work with GitHub and Travis, why not take advantage of that knowledge? 

Google App Engine on Travis CI

Setting up GAE on Travis is a bit tricky. There is no native support (like the one that exists for Heroku) so you have to do some extra work to use GAE with Travis. Doing with with the experimental Golang GAE runtime also adds some issues. The following is a simplified way of achieving a CI with GAE and Golang on Travis meant to give you some help when you begin to do this (not what I would use in production by copy-pasting!). 

As you may know if you have used Travis before, Travis works by inspecting a .travis.yml file, a YAML syntax config file that tells travis what to do in each step of the build. 

We like to execute scripts instead of writing inline code on our .travis file, so our file roughly looks something like this:
language: go
go:
- "1.2.1"

install: ./script/ci/install.sh
before_script: ./script/ci/before_script.sh
script: ./script/ci/script.sh
after_success: ./script/ci/after_success.sh 
the install.sh script is responsible for installing the GAE Golang SDK on travis looks roughly like this:
#!/bin/sh
echo "installing libs..."
cd ..
curl -O https://storage.googleapis.com/appengine-sdks/featured/go_appengine_sdk_linux_amd64-1.9.12.zip && unzip -q go_appengine_sdk_linux_amd64-1.9.12.zip
echo "installing libs fetched."
exit 0
The after_success script deploys the application to GAE (staging or production) and it looks something like this:
#!/bin/sh
export GAE_OAUTH=<your_oauth_token>
export GAE_DIR=../go_appengine
export APP_DIR=.
echo "PR# $TRAVIS_PULL_REQUEST"
python $GAE_DIR/appcfg.py --oauth2_refresh_token=$GAE_OAUTH update $APP_DIR
To generate your oauth 2 token ("passwordless authentication"), follow the instruction on GAE docs.

Golang Specific Google App Engine Tweaks for Travis CI

Google's GAE Golang runtime is using goapp, and not go executable for doing everything go in the GAE sandbox (including deployment!). This is, for example, to allow you fetching GAE packages that do not make much sense outside the GAE environment. 

The problem is: Travis does not like processes that exit with a non-zero status code, and sometime, the goapp get command that is used to fetch dependencies for your GAE go app will return a status code of 1. 

For example, importing package "code.google.com/p/goauth2/appengine/serviceaccount" will result in an error message: "imports code.google.com/p/goauth2/appengine/serviceaccount: no buildable Go source files". This will cause Travis to stop the build, even though the dependancy has been fetched and the application is GAE-buildable. 

To workaround that, the "before_script.sh" from above is used to fetch the dependencies (instead of inlining the goapp get statements in the .travis file), and it can goapp get the deps and "manually" return a status code of 0:

#!/bin/sh
echo "running goapp get to fetch dependencies..."
cd ./github.com/my_awesome_repo/appengine/my_awesome_package
../../../../../go_appengine/goapp get
echo "dependencies fetched."
exit 0
The above example always returns 0 (even if fetching the deps failed), but that's actually not that bad (because the complication would fail anyway if the dependencies had not been fetched). 

Finally, the script.sh file may be used as the place to run tests and build the app:

#!/bin/sh
echo "running tests and building..."
cd ./github.com/my_awesome_repo/appengine/my_awesome_package
../../../../../go_appengine/goapp test
../../../../../go_appengine/goapp build
Result:

:-)

No comments:

Post a Comment