Exercise 5: Extend the pipeline by adding further steps

Objective

Add another step to the .gitlab-ci.yml file to test the docker image itself, not just the executable.

Introduction

The test stage in the .gitlab-ci.yml file runs the hello executable directly, to check that it works. However, we then build and upload a docker image for it, but we don’t test the image. We can do that, to make sure it works correctly.

  • in the stages section, add a new stage, test-docker
  • below, add a new stanza for the test-docker stage.
    • give this step a meaningful name, e.g. run-docker, configure it to run the test-docker stage.
    • this will need to use the latest docker image and the docker-in-docker service, so make sure they’re declared.
    • you also need to set the dependencies correctly, so it has whichever artifacts it needs to download. (Hint: I’m misleading you here!)
    • then edit the script part, so that it does the docker login, and then just runs the container, like you did previously.
  • commit your code to the repository, add a new git tag for it, and push it, with the tag, to the server:
git add .
git commit -m "Add new section to test the docker image"
git tag v2.0
git push --tags

If you’re getting YAML errors, go to CI/CD -> Pipelines and look for the CI Lint button, top-right. There you can paste in the contents of your .gitlab-ci.yml file, click Validate, and see where it reports an error.

You can even edit in that window to try to correct the error, then when you’ve solved the problem, you can update your local file accordingly.

Conclusion

You now know how to add stages to your pipeline so you can implement complex builds and tests.

Best Practices

  • Each stage in your pipeline should perform a well-specified action, with a clear definition of success and failure. Don’t make your builds any more complex than necessary.
  • If your build is unavoidably complex, consider coding the build script as an external script, which you debug separately, rather than hand-coding it in the .gitlab-ci.yml file. This keeps the YAML file cleaner, at the expense of making it harder to see the details of what it’s actually doing.

Cheat-sheet

In case you get stuck, this is what your .gitlab-ci.yml file should now look like. Don’t peek until you’ve had a go yourself though!

> cat .gitlab-ci.yml
variables:
  DOCKER_TLS_CERTDIR: ""
  GIT_STRATEGY: clone
  REGISTRY_USER: tonywildish
  APPLICATION: tiny-test
  LATEST_IMAGE: $CI_REGISTRY/$REGISTRY_USER/$APPLICATION:latest
  RELEASE_IMAGE: $CI_REGISTRY/$REGISTRY_USER/$APPLICATION:$CI_BUILD_REF_NAME
  DOCKER_DRIVER: overlay

before_script:
  - echo "Starting..."
  - export DOCKER_IMAGE=$RELEASE_IMAGE
  - if [ "$CI_BUILD_REF_NAME" == "master" ]; then export DOCKER_IMAGE=$LATEST_IMAGE; fi
  - echo "Build docker image $DOCKER_IMAGE"

stages:
  - build
  - test
  - deploy
  - test-docker

compile:
  stage: build
  image: gcc:6
  services:
    - docker:dind
  artifacts:
    name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}"
    untracked: true
    expire_in: 1 week
  script:
    - make

run:
  stage: test
  dependencies:
  - compile
  only:
    - tags
  script:
    - echo "Testing application. First, list the files here, to show we have the git repo + the artifacts from the build step"
    - ls -l
    - echo 'Now try running it'
    - ./hello
    - echo "If that failed you won't see this because you'll have died already"

install:
  stage: deploy
  image: docker:latest
  services:
  - docker:dind
  dependencies:
  - compile
  script:
    - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
    - echo Building $DOCKER_IMAGE
    - docker build -t $DOCKER_IMAGE .
    - echo Deploying $DOCKER_IMAGE
    - docker push $DOCKER_IMAGE

run-docker:
  stage: test-docker
  image: docker:latest
  services:
  - docker:dind
  script:
    - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
    - docker run $DOCKER_IMAGE

after_script:
  - echo "Congratulations, this step succeeded"