To ensure that the build and test steps execute consistently, we should run them inside a container, which is an ephemeral, pre-configured, isolated environment.
A container is similar to a virtual machine, but uses fewer resources and is quicker to provision. Creating containers is cheap; this allows us to create containers, run the tests, and then discard them afterward.
Docker is the most popular container framework out there, and we will run our builds and tests inside Docker containers. In your repository, add the following Scripted Pipeline into a Jenkinsfile file:
node {
checkout scm
docker.image('docker.elastic.co/elasticsearch/elasticsearch-oss:6.3.2').withRun('-e "discovery.type=single-node"') { c ->
docker.image('node:8.11.4').inside("--link ${c.id}:db") {
withEnv(['SERVER_HOSTNAME=db',
'JENKINS=true',
'NODE_ENV=test',
'SERVER_PROTOCOL=http',
'SERVER_HOSTNAME=localhost',
'SERVER_PORT=8888',
'ELASTICSEARCH_PROTOCOL=http',
'ELASTICSEARCH_HOSTNAME=localhost',
'ELASTICSEARCH_PORT=9200',
'ELASTICSEARCH_INDEX=test']) {
stage('Waiting') {
sh 'until curl --silent $DB_PORT_9200_TCP_ADDR:$ELASTICSEARCH_PORT -w "" -o /dev/null; do sleep 1; done'
}
stage('Unit Tests') {
sh 'ELASTICSEARCH_HOSTNAME=$DB_PORT_9200_TCP_ADDR npm run test:unit'
}
stage('Integration Tests') {
sh 'ELASTICSEARCH_HOSTNAME=$DB_PORT_9200_TCP_ADDR npm run test:integration'
}
stage('End-to-End (E2E) Tests') {
sh 'ELASTICSEARCH_HOSTNAME=$DB_PORT_9200_TCP_ADDR npm run test:e2e'
}
}
}
}
}
The docker variable is provided by the Docker Pipeline plugin, and allows you to run Docker-related functions within a Pipeline. Here, we are using docker.image() to pull in an image. The image's withRun method will use docker run to run the image on the host.
Here, we are running the elasticsearch-oss image, and passing in the discovery.type flag—the same one that we've been using in previous chapters. Once the container is running, Jenkins will execute all the commands specified within the withRun block on the host, and then automatically exit once all the commands inside the body have finished.
Within the withRun block, we are specifying a docker.image().inside() block. Similar to withRun, commands inside the inside block will run once the container is up, but these instructions will run inside the container, instead of on the host. This is where we will run our tests.
Finally, we are passing in a --link flag to inside. This uses legacy Docker container links to provide our node:8.11.4 container with information about the elasticsearch-oss container, such as its address and ports. This allows our API application to connect to the database.
The --link flag has the following syntax:
--link <name or id>:alias
Here, <name or id> is the name or ID of the container we want to link to, and alias is a string that allows us to refer to this link by name. After withRun has successfully run a container, it will provide the body with a container object, c, which has an id property we can use in the link.
Once a container is linked, Docker will set several environment variables to provide information about the linked container. For instance, we can find out the IP address of the linked container by referring to the value of DB_PORT_9200_TCP_ADDR.
Save this Jenkinsfile and push it to the remote repository.