Jenkins build pipelines and PHP Unit tests and what Docker can do for you

Jenkins build pipelines and PHP Unit tests and what Docker can do for you

Stage phpunit-test

This stage runs all the PHP unit tests. To be sure to get always the same results the database is loaded with test data before the unit tests are started. In my case I have a PostgreSQL and a MariaDB which I have to prefill with data. As I don’t want to prepare test data for both databases I dumped my MariaDB with test data from the database server to XML. My idea was to use the following code for both database tests to seed the database with test data. If it would work I don’t have to take care about test data for both the PostgreSQL and MariaDB database.

public function getDataSet() {
   return $this->createMySQLXMLDataSet('./tests/files/fixture.xml');
}

To make a long story short. The code above works for me and I can fill PostgreSQL and MariaDB as well from the same XML test database dump.

try { 
     sh '''/usr/local/bin/phpunit.phar''' 
 } catch (any) {
     currentBuild.result = 'FAILURE'
     notifyEmail('FAILED')
     //timeout(time: 5, unit: 'DAYS') {
     // input message: 'Stop docker db container?', ok: 'Stop', submitter: 'who ever'
     //}
     dbContainer.stop()
     dbPgContainer.stop()
     throw any //rethrow exception to prevent the build from proceeding
 }

If anything failes during the unit tests, the catch block will take care to stop the Docker container and break the build. During my first steps with this Jenkins pipeline I had a lot of trouble with docker and the unit tests. With the commented timeout block I can stop the pipeline and wait for user input for – in my case – 5 days. During this time the Docker containers stay up and running. I can connect to the container and see what happened or investigate the Docker logs.

Stage stop-docker-container

If the init tests are all fine and there are no errors, the Docker containers are no longer needed. Therefore stop them.

try {
   dbContainer.stop()
   dbPgContainer.stop() 
} catch(all) { 
   currentBuild.result = 'FAILURE'
   notifyEmail('FAILED')
   throw any
}

The code above stops the Docker containers. If stopping fails the build will be stopped as well and a notification is dropped. In this case you may take care about the Docker containers yourself. If one of the containers is running it may cause problems as one of the ports needed for the database containers is not available during the next build run.

Stage SonarQube analysis

Sonarqube Sonar is a very well known tool to ensure code quality. There are metrics created informing about issues with the code. If the code is too bad the Quality Gate in the next stage will fail the build.

try {
    def scannerHome = tool 'SonarQube Scanner 2.6.1';
    withSonarQubeEnv('Sonar') {
        sh "${scannerHome}/bin/sonar-scanner -e -Dproject.settings=sonar-project.properties"
    }
} catch (any) {
     currentBuild.result = 'FAILURE'
     echo "sonarqube scan failed"
     notifyEmail('FAILED')
     throw any 
}

If the scan itself fails, the build will fail as well. The code above shows how to acces tools defined in Jenkins like the SonarQube Scanner.

Stages Quality Gate and docker-container-build

Running the SonarQube Sonar analysis takes some time. But why wait until the analysis if finished and the result of the code analysis is available? In parallel the build of the application Docker container can run as well to save time. If the code analysis fails due to bad code the Docker container build will fail as well. Someone might say why waste computing time in case of a failed code analysis? But in most cases the code analysis will not produce an error as you can ensure code quality during the development process.

parallel QualityGate: {
    //-------------------------------------------------------------------------------
    // wait for quality gate result
    //-------------------------------------------------------------------------------
    stage('Quality Gate') {
        timeout(time: 1, unit: 'HOURS') {
            def qg = waitForQualityGate()  
            if (qg.status != 'OK') { 
                notifyEmail('FAILED')
                error "Pipeline aborted due to quality gate failure: ${qg.status}"
            }
        }
     } // end: Quality Gate
 
}, DockerContainer: { 
    //-------------------------------------------------------------------------------
    // create docker container
    //-------------------------------------------------------------------------------
    stage('docker-container-build') {
 
         try {
             registry = String.valueOf(DOCKER_REGISTRY) + ":" + String.valueOf(DOCKER_REGISTRY_PORT)
             httpRegistry = "http://" + String.valueOf(DOCKER_REGISTRY) + ":" + String.valueOf(DOCKER_REGISTRY_PORT)
             sh 'tar cfz devops/application.tar.gz application'
 
             dir('devops') { 
                 docker.withRegistry(httpRegistry, "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") { 
                     imgObj = docker.build('application:' + dockerVersion) 
                     imgId = imgObj.id
                     echo "Image id: " + imgId  
                     imgObj.push()
                 }
             }
 
        } catch (any) {
            currentBuild.result = 'FAILURE'
            notifyEmail('FAILED')
            throw any 
        } 
     } // end: docker-container-build
 
}, failFast: true

The code shows how to run the Docker container build in parallel while waiting on the Sonarqube results. If one of the stages fails failFast: true the parallel Block fails immediately.

The devops directory contains the Dockerfile and all the other files needed to create a working Docker container from the application. The PHP application itself is packed into a tar.gz archive and stored in the devops folder. The Dockerfile requires this archive to copy it into the Docker container and unpack it into the webserver root.

Remaining stages

As you see from the picture above there are several other stages in my pipeline. All the stages dealing with artefacts, archives and RPMs run in parallel to the start of my application docker compose file on my Docker host. The pipeline code used is very similar to the code I showed above.

Sending notifications

I make it configurable by a Jenkins job choise parameter if emails on the job status should be sent. Slack notifications are always sent. I found some very useful code and hints on the website Sending Notifications in Pipeline.

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert