Continuous Integration with phpUnderControl and Git

I was looking for a decent continuous integration solution for my PHP projects for some time now, but always had the problem that most of the described solutions used SVN instead of Git as VCS system. Yesterday I found an article which describes the setup exactly as I needed it: phpUnderControl with Git on a Debian/Ubuntu system. Using the article, I managed to set up a working system quickly, which basically works as expected: CruiseControl checks the repository for modifications and starts the build process if there are any new commits. The build process includes generating API documentation (phpdocumentor), running static code analysis (php-codesniffer) and executing unit tests (phpunit). If the build succeeds, the results are published and can be accessed through a nice webinterface powered by phpUnderControl (see screenshot above which I stole from the phpUnderControl site).

However, the described setup has a few issues which bugged me:

  1. CruiseControl runs from the shellscript as root, posts all output to the console and is not automatically started at boot time.
  2. CruiseControl runs on port 8080, but I wanted to manage access to the webinterface through the apache which is already running on the box
  3. There’s no authentication – everybody can access my CI server, see the build details and start new builds through the webinterface.

I solved these issues with 2 steps.

Init script for CruiseControl

I wrote a simple init script which allows me to control CC. I’m not really into shellscripting and just hacked around on the apache init script until I got a working solution, so if you have any suggestions how to improve this script please let me know. The script implements the following functions: start, stop, restart, status. Save it to /etc/init.d/cruisecontrol and make it executable (chmod +x /etc/init.d/cruisecontrol).

#!/bin/sh
### BEGIN INIT INFO
# Provides:          cruisecontrol
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start/stop cruisecontrol CI server
### END INIT INFO
#
# cruisecontrol         This init.d script is used to start cruisecontrol.
#                       It basically just calls cruisecontrol.sh.

# ENV="env -i LANG=C PATH=/usr/local/bin:/usr/bin:/bin"

CCON_PATH=/opt/cruisecontrol
CCON_USER=www-data
PIDFILE=$CCON_PATH/cc.pid
LOGFILE=/var/log/cruisecontrol.log

. /lib/lsb/init-functions

ccon_is_running() {
    if [ -f $PIDFILE ]; then
       PID=`cat $PIDFILE`
       PID_COUNT=`ps aux | grep -E $PID | grep -v grep | wc -l`
       if [ $PID_COUNT = 1 ]; then
         return 1
       fi
    fi

    return 0
}

ccon_start() {
        ccon_is_running
        rc=$?
        if [ $rc -eq 1 ]; then
                log_failure_msg "CruiseControl is already running"
        else
                log_daemon_msg "Starting CI server" "CruiseControl"
                cd $CCON_PATH
                sudo -E -H -u $CCON_USER ./cruisecontrol.sh >> $LOGFILE 2>&1
                log_end_msg $?
        fi
}

ccon_stop() {
        ccon_is_running
        rc=$?
        if [ $rc -eq 1 ]; then
                log_daemon_msg "Stopping CI server" "CruiseControl"
                PID=`cat $PIDFILE`

                retval=0
                i=0
                while $(kill "$PID" 2> /dev/null);  do
                        if [ $i = '60' ]; then
                                echo ""
                                log_failure_msg "CruiseControl is taking too long to shutdown"
                                retval=1
                                break
                        else
                                if [ $i = '0' ]; then
                                        echo -n " ... waiting "
                                else
                                        echo -n "."
                                fi
                                i=$(($i+1))
                                sleep 1
                      fi
                 done

                log_end_msg $retval
        else
                log_failure_msg "CruiseControl is not running"
        fi
}

ccon_status() {
        ccon_is_running
        rc=$?
        if [ $rc -eq 1 ]; then
                PID=`cat $PIDFILE`
                log_success_msg "CruiseControl is running (pid $PID)."
        else
                log_failure_msg "CruiseControl is not running."
        fi

}

case $1 in
        start)
                ccon_start
        ;;
        stop)
                ccon_stop
        ;;
        restart)
                ccon_stop
                ccon_start
        ;;
        status)
                ccon_status
        ;;
        *)
                log_success_msg "Usage: /etc/init.d/cruisecontrol {start|stop|restart|status}"
                exit 1
        ;;
esac

Then change the ownership of the cruisecontrol installation to the user you specified in the init script (in my case www-data):

$ chown -R www-data.www-data /opt/cruisecontrol-bin-2.8.2/

You can now control CC just by using the init script.

$ /etc/init.d/cruisecontrol status
 * CruiseControl is not running.
$ /etc/init.d/cruisecontrol start
 * Starting CI server CruiseControl                        [ OK ]
$ /etc/init.d/cruisecontrol status
 * CruiseControl is running (pid 13721).
$ /etc/init.d/cruisecontrol stop
 * Stopping CI server CruiseControl
 ... waiting ..                                            [ OK ]
$ /etc/init.d/cruisecontrol status
 * CruiseControl is not running.

Additionally you can add it to the default runlevels to start it automatically on system boot.

$ update-rc.d cruisecontrol defaults

Access the webinterface via Apache

I wanted to use my existing apache installation to serve the webinterface. Doing this you can make the webinterface accessible through port 80, control authentication through apache’s various auth modules and eventually even use SSL to encrypt the traffic. The first thing I did was to bind the CruiseControl’s webserver (Jetty) to the local IP. Open /opt/cruisecontrol/etc/jetty.xml and find the following line:

<Set name="host"><SystemProperty name="jetty.host" /></Set>

Add the default attribute to that line and afterwards restart CC.

<Set name="host"><SystemProperty name="jetty.host" default="127.0.0.1" /></Set>

Now try to access CruiseControl through the external IP, you should not get a connection. But if you try to open a connection from the same host, you should get a response:

$ curl http://127.0.0.1:8080/cruisecontrol/

To serve this instance via apache you need 3 apache modules (install them via apt if you don’t have them installed):

$ a2enmod proxy proxy_http ext_filter

Create a new virtual host config file in /etc/apache2/sites-available/. Modify ServerName and ExtFilterDefine to match your environment.

<VirtualHost *:80>
        ServerName cruisecontrol.example.org

        ProxyRequests Off

        <Proxy *>
                Order deny,allow
                Allow from all
        </Proxy>

        ProxyPass / http://127.0.0.1:8080/
        ProxyPassReverse / http://127.0.0.1:8080/

        ExtFilterDefine fixurls mode=output intype=text/html cmd="/bin/sed s%http://127.0.0.1:8080%http://cruisecontrol.example.org%g"
        SetOutputFilter fixurls
</VirtualHost>

As a last step, enable the virtual host and restart apache (I named the config file cruisecontrol):

$ a2ensite cruisecontrol
$ /etc/init.d/apache2 restart

Now you should have access to your phpUnderControl installation via http://cruisecontrol.example.org/cruisecontrol. As a last step, we add basic apache authentication (replace with your preferred apache auth method). Edit the vhost config file and modify as follows:

        <Proxy *>
                Order deny,allow
                Allow from all

                AuthName        "phpUnderControl"
                AuthType        Basic
                AuthUserFile    /etc/apache2/cruisecontrol.htpasswd
                require         valid-user
        </Proxy>

As a last step, you have to create the .htpasswd file, which contains the user map.

$ htpasswd -c /etc/apache2/cruisecontrol.htpasswd myuser

Add additional users with the following command (same command but without the -c switch):

$ htpasswd /etc/apache2/cruisecontrol.htpasswd otheruser

Finally, restart apache and you are done:

$ /etc/init.d/apache2 restart

Sources