## 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" ```