diff --git a/hands-on/2023-11-30/010_jobs_and_stages/exercise/index.html b/hands-on/2023-11-30/010_jobs_and_stages/exercise/index.html index f7ca60dd..ee713b7a 100644 --- a/hands-on/2023-11-30/010_jobs_and_stages/exercise/index.html +++ b/hands-on/2023-11-30/010_jobs_and_stages/exercise/index.html @@ -1230,7 +1230,7 @@

Jobs and stagesPreparation

This workshop is based on an example hello world application written in Go. Get the code using the following command:

-
git checkout origin/160_gitlab_ci/example_app -- '*'
+
git checkout upstream/160_gitlab_ci/example_app -- '*'
 

Task 1: Create a single job

Add a pipeline to build the code using the following commands:

@@ -1264,7 +1264,7 @@

Task 1: Create a single job - ./hello

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/010_jobs_and_stages/build -- '*'
+
git checkout upstream/160_gitlab_ci/010_jobs_and_stages/build -- '*'
 

Task 2: Add a stage

@@ -1325,7 +1325,7 @@

Task 2: Add a stage - ./hello

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/010_jobs_and_stages/lint -- '*'
+
git checkout upstream/160_gitlab_ci/010_jobs_and_stages/lint -- '*'
 

Task 3: Add parallel jobs

@@ -1389,7 +1389,7 @@

Task 3: Add parallel jobs - ./hello

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/010_jobs_and_stages/parallel -- '*'
+
git checkout upstream/160_gitlab_ci/010_jobs_and_stages/parallel -- '*'
 
diff --git a/hands-on/2023-11-30/020_variables/exercise/index.html b/hands-on/2023-11-30/020_variables/exercise/index.html index 3190df6c..8a1b1195 100644 --- a/hands-on/2023-11-30/020_variables/exercise/index.html +++ b/hands-on/2023-11-30/020_variables/exercise/index.html @@ -1212,7 +1212,7 @@

VariablesTask 1: Create a job variable

This exercise requires an updates version of our hello world program:

-
git checkout origin/160_gitlab_ci/020_variables/inline -- main.go
+
git checkout upstream/160_gitlab_ci/020_variables/inline -- main.go
 

Add a variable called version to the job called build and modify the build command as follows:

go build -o hello -ldflags "-X main.Version=${version}" .
@@ -1292,7 +1292,7 @@ 

Task 1: Create a job variable - ./hello

If you want to jump to the solution, execute the following command:

-

git checkout origin/160_gitlab_ci/020_variables/inline -- '*'

+

git checkout upstream/160_gitlab_ci/020_variables/inline -- '*'

Task 2: Use a predefined variable

Read the official documentation about predefined variables and replace the job variable with the predefined variable CI_COMMIT_REF_NAME.

@@ -1366,11 +1366,11 @@

Task 2: Use a predefined variable - ./hello

If you want to jump to the solution, execute the following command:

-

git checkout origin/160_gitlab_ci/020_variables/predefined -- '*'

+

git checkout upstream/160_gitlab_ci/020_variables/predefined -- '*'

Task 3: Add a CI variable in the UI

This exercise requires an updates version of our hello world application:

-
git checkout origin/160_gitlab_ci/020_variables/ci -- main.go
+
git checkout upstream/160_gitlab_ci/020_variables/ci -- main.go
 

The application now also prints the name of the author which must be supplied during compilation as well.

Read the official documentation about CI variables and extend the build command to provide main.Author through a CI variable called AUTHOR.

@@ -1448,7 +1448,7 @@

Task 3: Add a CI variable in the UI< - ./hello

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/020_variables/ci -- '*'
+
git checkout upstream/160_gitlab_ci/020_variables/ci -- '*'
 
diff --git a/hands-on/2023-11-30/030_script_blocks/exercise/index.html b/hands-on/2023-11-30/030_script_blocks/exercise/index.html index c8f60133..32346731 100644 --- a/hands-on/2023-11-30/030_script_blocks/exercise/index.html +++ b/hands-on/2023-11-30/030_script_blocks/exercise/index.html @@ -1285,7 +1285,7 @@

Task: Separa - ./hello

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/030_script_blocks -- '*'
+
git checkout upstream/160_gitlab_ci/030_script_blocks -- '*'
 

Cleanup commands can be move to after_script (official documentation) but we have no use for this in the current example.

diff --git a/hands-on/2023-11-30/040_image/exercise/index.html b/hands-on/2023-11-30/040_image/exercise/index.html index 9ad25568..66289acf 100644 --- a/hands-on/2023-11-30/040_image/exercise/index.html +++ b/hands-on/2023-11-30/040_image/exercise/index.html @@ -1258,7 +1258,7 @@

Task: Simplify using container ima - ./hello

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/040_image -- '*'
+
git checkout upstream/160_gitlab_ci/040_image -- '*'
 

Bonus: Test different images

diff --git a/hands-on/2023-11-30/050_defaults/exercise/index.html b/hands-on/2023-11-30/050_defaults/exercise/index.html index 625d88ed..5c169a88 100644 --- a/hands-on/2023-11-30/050_defaults/exercise/index.html +++ b/hands-on/2023-11-30/050_defaults/exercise/index.html @@ -1276,7 +1276,7 @@

Task: Don't repeat yourself - ./hello

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/050_default -- '*'
+
git checkout upstream/160_gitlab_ci/050_default -- '*'
 

Bonus 1: Override defaults

diff --git a/hands-on/2023-11-30/060_artifacts/exercise/index.html b/hands-on/2023-11-30/060_artifacts/exercise/index.html index ce242ab2..42b16e59 100644 --- a/hands-on/2023-11-30/060_artifacts/exercise/index.html +++ b/hands-on/2023-11-30/060_artifacts/exercise/index.html @@ -1303,7 +1303,7 @@

Task 1: Pass an artifact to t - ./hello

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/060_artifact -- '*'
+
git checkout upstream/160_gitlab_ci/060_artifact -- '*'
 

Bonus 1: Define from which jobs to receive artifacts

diff --git a/hands-on/2023-11-30/090_unit_tests/exercise/index.html b/hands-on/2023-11-30/090_unit_tests/exercise/index.html index 637be04b..705c7bd2 100644 --- a/hands-on/2023-11-30/090_unit_tests/exercise/index.html +++ b/hands-on/2023-11-30/090_unit_tests/exercise/index.html @@ -1194,7 +1194,7 @@

Unit testsPreparation

Let's update the code:

-
git checkout origin/160_gitlab_ci/090_unit_tests -- main_test.go go.mod go.sum
+
git checkout upstream/160_gitlab_ci/090_unit_tests -- main_test.go go.mod go.sum
 

Task: Publish unit test results

The following commands execute unit tests and automatically convert the results to JUnit using gotestsum:

@@ -1316,7 +1316,7 @@

Task: Publish unit test results - ./hello

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/090_unit_tests -- '*'
+
git checkout upstream/160_gitlab_ci/090_unit_tests -- '*'
 
diff --git a/hands-on/2023-11-30/100_environments/exercise/index.html b/hands-on/2023-11-30/100_environments/exercise/index.html index 9436f89d..0d0abcd0 100644 --- a/hands-on/2023-11-30/100_environments/exercise/index.html +++ b/hands-on/2023-11-30/100_environments/exercise/index.html @@ -1383,7 +1383,7 @@

Task 1: Add target environment --user seat${SEAT_INDEX}:${PASS}

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/100_environments/demo1 -- '*'
+
git checkout upstream/160_gitlab_ci/100_environments/demo1 -- '*'
 

Task 2: Add deployment to development environment

@@ -1514,7 +1514,7 @@

Task 2: Add deployment --user seat${SEAT_INDEX}:${PASS}

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/100_environments/demo2 -- '*'
+
git checkout upstream/160_gitlab_ci/100_environments/demo2 -- '*'
 

This was just a demonstration. The changes will not be preseved in the following chapters.

diff --git a/hands-on/2023-11-30/110_triggers/exercise/index.html b/hands-on/2023-11-30/110_triggers/exercise/index.html index c486d43e..a8dea32b 100644 --- a/hands-on/2023-11-30/110_triggers/exercise/index.html +++ b/hands-on/2023-11-30/110_triggers/exercise/index.html @@ -1409,7 +1409,7 @@

Task 1: Using a trigger token -F "ref=main"

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/110_triggers/curl -- '*'
+
git checkout upstream/160_gitlab_ci/110_triggers/curl -- '*'
 

This was just a demonstration. The changes will not be preserved in the following chapters.

@@ -1712,7 +1712,7 @@

Task 3: Using a parent-child pipel include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/110_triggers/parent-child -- '*'
+
git checkout upstream/160_gitlab_ci/110_triggers/parent-child -- '*'
 
diff --git a/hands-on/2023-11-30/120_templates/exercise/index.html b/hands-on/2023-11-30/120_templates/exercise/index.html index 24ea416d..a8a3d5c9 100644 --- a/hands-on/2023-11-30/120_templates/exercise/index.html +++ b/hands-on/2023-11-30/120_templates/exercise/index.html @@ -1371,7 +1371,7 @@

Task 1: Create a template inline

You decide whether artifacts is part of the template or not!

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/120_templates/inline -- '*'
+
git checkout upstream/160_gitlab_ci/120_templates/inline -- '*'
 

Task 2: Loading templates from a local file

@@ -1534,7 +1534,7 @@

Task 2: Loading templates fr include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/120_templates/local -- '*'
+
git checkout upstream/160_gitlab_ci/120_templates/local -- '*'
 

Task 3: Loading templates from another project

diff --git a/hands-on/2023-11-30/130_rules/exercise/index.html b/hands-on/2023-11-30/130_rules/exercise/index.html index 4675a9a7..1bf7f4f2 100644 --- a/hands-on/2023-11-30/130_rules/exercise/index.html +++ b/hands-on/2023-11-30/130_rules/exercise/index.html @@ -1231,7 +1231,7 @@

Rules&

In this exercise we will publish a static web page to download the hello binary.

Preparation

Add a file public/index.html to your project using the following command:

-
git checkout origin/160_gitlab_ci/130_rules -- 'public/index.html'
+
git checkout upstream/160_gitlab_ci/130_rules -- 'public/index.html'
 

Task 1: Prevent a job from running

Add a job pages to the stage deploy with the following content:

@@ -1415,7 +1415,7 @@

Task 1: Prevent a job from running include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/130_templates -- '*'
+
git checkout upstream/160_gitlab_ci/130_templates -- '*'
 

Task 2: Prevent a pipeline from running

@@ -1611,7 +1611,7 @@

Task 2: Prevent a pipeline from include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/130_rules_workflow -- '*'
+
git checkout upstream/160_gitlab_ci/130_rules_workflow -- '*'
 

Task 3: Use deploy freeze

diff --git a/hands-on/2023-11-30/140_merge_requests/exercise/index.html b/hands-on/2023-11-30/140_merge_requests/exercise/index.html index 6cb3b0e9..0ea5eef3 100644 --- a/hands-on/2023-11-30/140_merge_requests/exercise/index.html +++ b/hands-on/2023-11-30/140_merge_requests/exercise/index.html @@ -1467,7 +1467,7 @@

Task 1: Use rules to r include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/140_merge_requests -- '*'
+
git checkout upstream/160_gitlab_ci/140_merge_requests -- '*'
 

Task 2: Create a merge request

@@ -1721,7 +1721,7 @@

Task 3: Avoid repetition u include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/140_merge_requests_rule_templates -- '*'
+
git checkout upstream/160_gitlab_ci/140_merge_requests_rule_templates -- '*'
 
diff --git a/hands-on/2023-11-30/150_matrix_jobs/exercise/index.html b/hands-on/2023-11-30/150_matrix_jobs/exercise/index.html index d7257964..ee59e2b2 100644 --- a/hands-on/2023-11-30/150_matrix_jobs/exercise/index.html +++ b/hands-on/2023-11-30/150_matrix_jobs/exercise/index.html @@ -1499,7 +1499,7 @@

Task 1: Build binary for mul include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/150_matrix_jobs_demo1 -- '*'
+
git checkout upstream/160_gitlab_ci/150_matrix_jobs_demo1 -- '*'
 

Task 2: Test an alternative to specify the same inputs

@@ -1799,7 +1799,7 @@

Bonus: Check binaries for cor include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/150_matrix_jobs_demo2 -- '*'
+
git checkout upstream/160_gitlab_ci/150_matrix_jobs_demo2 -- '*'
 
diff --git a/hands-on/2023-11-30/220_services/exercise/index.html b/hands-on/2023-11-30/220_services/exercise/index.html index 63b86937..c32ab87e 100644 --- a/hands-on/2023-11-30/220_services/exercise/index.html +++ b/hands-on/2023-11-30/220_services/exercise/index.html @@ -1448,7 +1448,7 @@

Task 1: Create and use a service include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/220_services -- '*'
+
git checkout upstream/160_gitlab_ci/220_services -- '*'
 

Task 2: Move the service into the job

diff --git a/hands-on/2023-11-30/230_docker/exercise/index.html b/hands-on/2023-11-30/230_docker/exercise/index.html index 99694792..4c659c56 100644 --- a/hands-on/2023-11-30/230_docker/exercise/index.html +++ b/hands-on/2023-11-30/230_docker/exercise/index.html @@ -1193,7 +1193,7 @@

DockerPreparation

Building a container image requires a Dockerfile which can be fetched with the following command:

-
git checkout origin/160_gitlab_ci/230_docker -- Dockerfile
+
git checkout upstream/160_gitlab_ci/230_docker -- Dockerfile
 

Task: Build a container image

For building a container image, you will need to...

@@ -1471,7 +1471,7 @@

Task: Build a container image include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/230_docker -- '*'
+
git checkout upstream/160_gitlab_ci/230_docker -- '*'
 
diff --git a/hands-on/2023-11-30/240_registries/exercise/index.html b/hands-on/2023-11-30/240_registries/exercise/index.html index 40afacdc..d41b5f66 100644 --- a/hands-on/2023-11-30/240_registries/exercise/index.html +++ b/hands-on/2023-11-30/240_registries/exercise/index.html @@ -1451,7 +1451,7 @@

Task: Push a container image include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/240_registries -- '*'
+
git checkout upstream/160_gitlab_ci/240_registries -- '*'
 
diff --git a/hands-on/2023-11-30/250_releases/exercise/index.html b/hands-on/2023-11-30/250_releases/exercise/index.html index c0dba53f..6f820085 100644 --- a/hands-on/2023-11-30/250_releases/exercise/index.html +++ b/hands-on/2023-11-30/250_releases/exercise/index.html @@ -1471,7 +1471,7 @@

Task: Create a release include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/250_releases -- '*'
+
git checkout upstream/160_gitlab_ci/250_releases -- '*'
 
diff --git a/hands-on/2023-11-30/265_caches/exercise/index.html b/hands-on/2023-11-30/265_caches/exercise/index.html index 107aaacc..a0612e71 100644 --- a/hands-on/2023-11-30/265_caches/exercise/index.html +++ b/hands-on/2023-11-30/265_caches/exercise/index.html @@ -1327,7 +1327,7 @@

Task 2: Add caching file hello-${GOOS}-${GOARCH}

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/265_caches -- '*'
+
git checkout upstream/160_gitlab_ci/265_caches -- '*'
 
diff --git a/hands-on/2023-11-30/270_renovate/exercise/index.html b/hands-on/2023-11-30/270_renovate/exercise/index.html index a3a18ea8..c6d6b2e8 100644 --- a/hands-on/2023-11-30/270_renovate/exercise/index.html +++ b/hands-on/2023-11-30/270_renovate/exercise/index.html @@ -1490,7 +1490,7 @@

Task: Add Renovate to your pipeline< include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/270_renovate -- '*'
+
git checkout upstream/160_gitlab_ci/270_renovate -- '*'
 
diff --git a/hands-on/2023-11-30/280_security/exercise/index.html b/hands-on/2023-11-30/280_security/exercise/index.html index 063ef397..b95ada6f 100644 --- a/hands-on/2023-11-30/280_security/exercise/index.html +++ b/hands-on/2023-11-30/280_security/exercise/index.html @@ -1508,7 +1508,7 @@

Task: Add integrated security scans< include: child.yaml

If you want to jump to the solution, execute the following command:

-
git checkout origin/160_gitlab_ci/280_security -- '*'
+
git checkout upstream/160_gitlab_ci/280_security -- '*'
 
diff --git a/hands-on/2023-11-30/search/search_index.json b/hands-on/2023-11-30/search/search_index.json index b442bf10..94784594 100644 --- a/hands-on/2023-11-30/search/search_index.json +++ b/hands-on/2023-11-30/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Workshop: GitLab CI","text":"

This site contains the exercises to learn about GitLab CI.

"},{"location":"#audience","title":"Audience","text":"

Software developers and system administrators who want to learn how to use GitLab CI.

"},{"location":"#duration","title":"Duration","text":"

Two days

"},{"location":"#expected-knowledge","title":"Expected knowledge","text":"

Basic under standing of another CI/CD server, e.g. GitHub and Jenkins

"},{"location":"#goals","title":"Goals","text":"

Learn about the features of GitLab CI and how to use then in a software development project.

"},{"location":"#style","title":"Style","text":"

20% theoretical introduction

80% practical exercises

The instructor will provide an introduction to each topic. Participants will then work on exercises. The instructor will be available to answer questions and provide hints. The instructor will also demonstrate solutions to the exercises.

"},{"location":"navigation/","title":"Navigation","text":"

All pages have the same navigation structure.

"},{"location":"navigation/#section-1-menu-on-the-left","title":"Section 1: Menu on the left","text":"

The left-hand menu contains the available chapters and displays the structure of the workshop.

On smaller screens or windows, the left-hand menu can be shown by clicking on the burger menu .

"},{"location":"navigation/#section-2-page-contents-on-the-right","title":"Section 2: Page contents on the right","text":"

The right-hand menu contains the contents of the current chapter and lists all exercises contained in the current chapter.

On smaller screens or windows, the right-hand menu can be shown by clicking on the burger menu and then clicking on the icon next to the current chapter.

"},{"location":"you_are_done/","title":"You are done!","text":""},{"location":"you_are_done/#congratulations","title":"Congratulations!","text":"

You have finished the workshop

"},{"location":"you_are_ready/","title":"You are ready!","text":"

A few last words...

"},{"location":"you_are_ready/#exercises-tell-a-story","title":"Exercises tell a story","text":"

Each chapter focuses on a single feature

Each exercise improves the previous state

All exercises will have leave some questions unanswered

Following exercises will again improve

"},{"location":"you_are_ready/#work-at-your-own-pace","title":"Work at your own pace","text":"

Either follow topics and exercises at the instructor's pace

Or work ahead if you finish early

Please be mindful of other participants

Please do not confuse participants with your questions when racing ahead

"},{"location":"000_rollout/exercise_gitlab/","title":"GitLab","text":"

This workshop is performed on a shared GitLab instance. You have been assigned a user and password for this instance in an email.

"},{"location":"000_rollout/exercise_gitlab/#task-1-retrieve-your-credentials-digitally","title":"Task 1: Retrieve your credentials digitally","text":"

You have received your credentials in an email but the password is not fun to type in. Let's retrieve it digitally:

  1. Go to https://code.inmylab.de
  2. Select your username from the list
  3. Login using your personal user seatN (where N is a number) and your code (looks like this ABCDEF)
  4. The web page displays your credentials

Keep the page open to refer to your credentials anytime throughout the workshop.

"},{"location":"000_rollout/exercise_gitlab/#task-2-login-to-gitlab","title":"Task 2: Login to GitLab","text":"

Go to https://gitlab.inmylab.de and login with your credentials.

Hint (Click if you are stuck)

Your username looks like seatN where N is a number.

Your password is a long, random string which is displayed on the web pages access in the previous task.

"},{"location":"000_rollout/exercise_gitlab/#task-3-access-the-demo-project","title":"Task 3: Access the demo project","text":"

A personal project has been provisioned for you to follow this workshop. It is called demo. Let's find it!

Hint (Click if you are stuck)

Personal projects are access by clicking on your avatar in the top left corner.

Solution (Click if you are stuck)

The deep link is https://gitlab.inmylab.de/seatN/demo where N is a number.

"},{"location":"000_rollout/exercise_ide/","title":"IDE","text":"

You can follow this workshop in the IDE (Integrated Development Environment) of your choice but...

A web-based instance of Visual Studio Code has been provisioned for you to follow this workshop.

"},{"location":"000_rollout/exercise_ide/#task-access-your-instance-of-visual-studio-code","title":"Task: Access your instance of Visual Studio Code","text":"
  1. Go to https://seatN.vscode.inmylab.de where N is a number.
  2. Login using your personal user seatN (where N is a number) and your password

The password is the same as your GitLab password.

Hint (Click if you are stuck)

Your username looks like seatN where N is a number.

Your password is a long, random string which is displayed on the web pages access in the previous chapter.

"},{"location":"000_rollout/exercise_project/","title":"Project","text":""},{"location":"000_rollout/exercise_project/#task-1-view-the-demo-project","title":"Task 1: View the demo project","text":"

Your instance of Visual Studio Code is provisioned with your credentials to allow Git-over-HTTP to the GitLab instance as well as a clone of the demo project.

  1. In the directory tree
  2. Find the demo project under /home/seat
"},{"location":"000_rollout/exercise_project/#task-2-pulling-from-the-upstream-repository","title":"Task 2: Pulling from the upstream repository","text":"

The local clone of the demo project is configured with a remote pointing to the location of the original project on GitHub. This is prepared in case the instructor needs to fix a demo. Let's test this safeguard:

  1. Open a terminal
  2. Change to the demo project directory
  3. List remotes: git remote -v
  4. Pull from the upstream repository: git pull upstream
"},{"location":"010_jobs_and_stages/exercise/","title":"Jobs and stages","text":"

Goal

Learn how to...

  • create jobs
  • organize them in stages
  • understand when jobs in different stages are executed
"},{"location":"010_jobs_and_stages/exercise/#preparation","title":"Preparation","text":"

This workshop is based on an example hello world application written in Go. Get the code using the following command:

git checkout origin/160_gitlab_ci/example_app -- '*'\n
"},{"location":"010_jobs_and_stages/exercise/#task-1-create-a-single-job","title":"Task 1: Create a single job","text":"

Add a pipeline to build the code using the following commands:

apk update\napk add go\ngo build -o hello .\n./hello\n

See the official documentation about jobs.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Add a file called .gitlab-ci.yml in the root of the project
  2. Add a job called build
Solution (Click if you are stuck)

.gitlab-ci.yml:

build:\n  script:\n  - apk update\n  - apk add go\n  - go build -o hello .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/010_jobs_and_stages/build -- '*'\n
"},{"location":"010_jobs_and_stages/exercise/#task-2-add-a-stage","title":"Task 2: Add a stage","text":"

Modify the pipeline to consist of two stages called check and build where the check stage contains the following commands:

apk update\napk add go\ngo fmt .\ngo vet .\n

See the official documentation about stages.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Define two stages using stages
  2. Add a job called check in the stage check
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - apk update\n  - apk add go\n  - go build -o hello .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/010_jobs_and_stages/lint -- '*'\n
"},{"location":"010_jobs_and_stages/exercise/#task-3-add-parallel-jobs","title":"Task 3: Add parallel jobs","text":"

Split the job check so that one job called lint executes go fmt . and another job called audit executes go vet ..

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Both jobs lint and audit must be in the stage check.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - apk update\n  - apk add go\n  - go build -o hello .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/010_jobs_and_stages/parallel -- '*'\n
"},{"location":"020_variables/exercise/","title":"Variables","text":"

Goal

Learn how to...

  • add local variable to your pipeline
  • consume pre-defined variables
  • add secrets in the UI
"},{"location":"020_variables/exercise/#task-1-create-a-job-variable","title":"Task 1: Create a job variable","text":"

This exercise requires an updates version of our hello world program:

git checkout origin/160_gitlab_ci/020_variables/inline -- main.go\n

Add a variable called version to the job called build and modify the build command as follows:

go build -o hello -ldflags \"-X main.Version=${version}\" .\n

See the official documentation about variables.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Use the variable keyword to define a variable inside the job called build
  2. Replace the build command with the one provided above
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go vet .\n\nbuild:\n  stage: build\n  variables:\n    version: dev\n  script:\n  - apk update\n  - apk add go\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${version}\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/020_variables/inline -- '*'

"},{"location":"020_variables/exercise/#task-2-use-a-predefined-variable","title":"Task 2: Use a predefined variable","text":"

Read the official documentation about predefined variables and replace the job variable with the predefined variable CI_COMMIT_REF_NAME.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Remove the variable keyword from the job called build
  2. Replace the variable ${version} with the predefined variable ${CI_COMMIT_REF_NAME}
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - apk update\n  - apk add go\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME}\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/020_variables/predefined -- '*'

"},{"location":"020_variables/exercise/#task-3-add-a-ci-variable-in-the-ui","title":"Task 3: Add a CI variable in the UI","text":"

This exercise requires an updates version of our hello world application:

git checkout origin/160_gitlab_ci/020_variables/ci -- main.go\n

The application now also prints the name of the author which must be supplied during compilation as well.

Read the official documentation about CI variables and extend the build command to provide main.Author through a CI variable called AUTHOR.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint 1 (Click if you are stuck)
  1. Go to Settings > CI/CD > Variables
  2. Add a variable called AUTHOR with your name
Hint 2 (Click if you are stuck)

The -ldflags option needs to be extended with -X 'main.Author=${AUTHOR}'

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - apk update\n  - apk add go\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/020_variables/ci -- '*'\n
"},{"location":"030_script_blocks/exercise/","title":"Scriptblocks","text":"

Goal

Learn how to...

  • Use before_script and after_script
  • Separate preparation and cleanup commands from core functionality
"},{"location":"030_script_blocks/exercise/#task-separate-script-blocks-into-preparation-and-main-task","title":"Task: Separate script blocks into preparation and main task","text":"

Commands are currently specified using the script directive. These commands consist of preparation, core functionality and (possibly) cleanup.

To improve readability, move the preparation of the execution environment to a before_script. See the official documentation.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Move calls to apk to the before_script.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  before_script:\n  - apk update\n  - apk add go\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  before_script:\n  - apk update\n  - apk add go\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  before_script:\n  - apk update\n  - apk add go\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/030_script_blocks -- '*'\n

Cleanup commands can be move to after_script (official documentation) but we have no use for this in the current example.

"},{"location":"030_script_blocks/exercise/#bonus-1-when-after_script-is-executed","title":"Bonus 1: When after_script is executed","text":"

Add commands to all three script block before_script, script and after_script. Test two scenarios:

  1. The pipeline succeeds
  2. The pipeline failes

What happens to the code in after_script?

Solution (Click to reveal)

Command in after_script are always executed even if the job fails.

This can be very useful for cleaning up.

"},{"location":"030_script_blocks/exercise/#bonus-2-what-happens-to-environment-variables-in-script-blocks","title":"Bonus 2: What happens to environment variables in script blocks?","text":"

Define environment variables in all three script blocks and display them in the same and in the following script block.

When are environment variables available?

Solution (Click to reveal)

Commands in before_script and script share a shell session. Environment variables are available throughout these script blocks.

Commands in after_script are executed in a new shell session. Environment variables defined in before_script and script are gone.

"},{"location":"040_image/exercise/","title":"Images","text":"

Goal

Learn how to...

  • specify which container image to use for a job
  • tailor the execution environment to your needs
"},{"location":"040_image/exercise/#task-simplify-using-container-images","title":"Task: Simplify using container images","text":"

In the previous exampes, we called apk at the beginning of every job to install Go. This had to be repeated for every job because Go was not present. Choosing an image for a job using the image directive, time is saved by avoiding commands to install required tools. See the official documentation.

Replace the calls to apk with the container image golang:1.19.2.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  • Remove before_script
  • Add image: golang:1.19.2 instead
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  image: golang:1.19.2\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  image: golang:1.19.2\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  image: golang:1.19.2\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/040_image -- '*'\n
"},{"location":"040_image/exercise/#bonus-test-different-images","title":"Bonus: Test different images","text":"

Add a job to your pipeline to test different container images. Check how different images offer specialized execution environments:

  1. Use python:3 and test running python --version
  2. Use node and test running node --version
"},{"location":"050_defaults/exercise/","title":"Defaults","text":"

Goal

Learn how to...

  • avoid repetition in jobs
  • specify defaults are the top of your pipeline
"},{"location":"050_defaults/exercise/#task-dont-repeat-yourself","title":"Task: Don't repeat yourself","text":"

All jobs currently have a dedicated image directive. Using defaults, this repetition can be avoided. See the official documentation.

Replace job specific image directives with the default directive.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Remove image from all build jobs
  2. Add default with the image directive at the top
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/050_default -- '*'\n
"},{"location":"050_defaults/exercise/#bonus-1-override-defaults","title":"Bonus 1: Override defaults","text":"

Jobs can still choose to use an image different from the default:

  1. Add a new job
  2. Add an image directory to the new job
  3. Specify a different image
  4. Check out how the executation environment changes
"},{"location":"050_defaults/exercise/#bonus-2-default-values-for-variables","title":"Bonus 2: Default values for variables","text":"

See the official documentation for default as well as variables and check how they are related.

Solution (Click if you are stuck)

Global variables are not located under default but under the global variables keyword.

"},{"location":"060_artifacts/exercise/","title":"Artifacts","text":"

Goal

Learn how to...

  • define artifacts
  • consume artifacts
"},{"location":"060_artifacts/exercise/#task-1-pass-an-artifact-to-the-next-stage","title":"Task 1: Pass an artifact to the next stage","text":"

Artifacts are useful for splitting a task in separate job. Refer to the official documentation.

Improve the pipeline by using artifacts:

  1. Create an artifact from the hello binary
  2. Create a new stage called test with a job called test
  3. Call the hello binary as a smoke test

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Example for creating an artifacts:

job_name:\n  script:\n  - echo foo >file.txt\n  artifacts:\n    paths:\n    - file.txt\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/060_artifact -- '*'\n
"},{"location":"060_artifacts/exercise/#bonus-1-define-from-which-jobs-to-receive-artifacts","title":"Bonus 1: Define from which jobs to receive artifacts","text":"

Usually, artifacts are received from all jobs in the previous stages. Decide from which jobs to receive artifacts using the dependencies keyword. See the official documentation.

Modify the job test to consume artifacts only from the job build.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  dependencies:\n  - build\n  script:\n  - ./hello\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"060_artifacts/exercise/#bonus-2-passing-environment-variables","title":"Bonus 2: Passing environment variables","text":"

In some situations, artifacts are to heavy-weight and passing a variable would be enough. Read the documentation for passing environment variables and implement this between two jobs of your choice.

The following hint and solution are a working example.

Hint (Click if you are stuck)

Example for creating an artifact for environment variables:

job_name:\n  script:\n  - echo \"foo=bar\" >build.env\n  artifacts:\n    reports:\n      dotenv: build.env\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  variables:\n    BINARY_NAME: hello\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o \"${BINARY_NAME}\" \\\n        .\n  - echo \"${BINARY_NAME}\" >build.env\n  artifacts:\n    paths:\n    - hello\n    reports:\n      dotenv: build.env\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./${BINARY_NAME}\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"065_job_dependencies/exercise/","title":"Job dependencies","text":"

Goal

Learn how to...

  • ignore stages
  • start jobs as soon as dependencies are met
"},{"location":"065_job_dependencies/exercise/#task-start-a-job-early","title":"Task: Start a job early","text":"

Start the job build as soon as the job audit completes without waiting for other job of the stage check to finish. Check out the official documentation of needs.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  needs:\n  - audit\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"065_job_dependencies/exercise/#bonus-start-a-job-late","title":"Bonus: Start a job late","text":"

If two jobs in the same stage should not be executed at the same time, the needs keyword can also delay a job until the dependencies are met. Modify the job lint so that it waits for the job audit to finish.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  needs:\n  - audit\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  needs:\n  - audit\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"070_schedules/exercise/","title":"Schedules","text":"

Goal

Learn how to...

  • schedule a pipeline run
  • use a schedule to execute a preconfigured pipeline
"},{"location":"070_schedules/exercise/#task-create-a-schedule","title":"Task: Create a schedule","text":"

Create a schedule to run a 5 minutes on the branch main in the correct timezone. See the official documentation for schedules.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

"},{"location":"070_schedules/exercise/#bonus-start-a-schedule-manually","title":"Bonus: Start a schedule manually","text":"

Run the previously created schedule manually by clicking the play button . This come in handy if you need to run a pipeline with pre-configured variables.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

"},{"location":"090_unit_tests/exercise/","title":"Unit tests","text":"

Goal

Learn how to...

  • execute unit tests
  • publish results in GitLab

This exercise adds a unit test to the hello world application.

"},{"location":"090_unit_tests/exercise/#preparation","title":"Preparation","text":"

Let's update the code:

git checkout origin/160_gitlab_ci/090_unit_tests -- main_test.go go.mod go.sum\n
"},{"location":"090_unit_tests/exercise/#task-publish-unit-test-results","title":"Task: Publish unit test results","text":"

The following commands execute unit tests and automatically convert the results to JUnit using gotestsum:

go install gotest.tools/gotestsum@latest\ngotestsum --junitfile report.xml\n

See the official documentation for special artifacts and specifically reports.

Add a job unit_test to the stage check containing the above commands. The job needs to define a special artifact from the file report.xml so that GitLab recognizes it as as JUnit XML report.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run which shows the unit test results on the tab in the overview.

Hint (Click if you are stuck)

GitLab has published an example. The unit test report is published using a special type of artifact:

build:\n  stage: test\n  script: echo\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/090_unit_tests -- '*'\n
"},{"location":"100_environments/exercise/","title":"Environments","text":"

Goal

Learn how to...

  • use environments to specify deployment targets
  • select environments dynamically
"},{"location":"100_environments/exercise/#preparation","title":"Preparation","text":"

Create CI variables for use in the following exercises:

  1. Retrieve passwords for dev and live environments from the info page
  2. Create unprotected but masked CI variable PASS twice with scope dev and live
  3. Create unprotected CI variable SEAT_INDEX with your seat number
"},{"location":"100_environments/exercise/#task-1-add-target-environment","title":"Task 1: Add target environment","text":"

Add a new stage deploy with a job called deploy and use the following commands to upload the binary to the dev environment:

curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n    --fail \\\n    --verbose \\\n    --upload-file hello \\\n    --user seat${SEAT_INDEX}:${PASS}\n

Mind that curl is not available in the default image golang:1.19.2 but must be installed using the following commands. Apply what you learned about script blocks as well as separating commands into preparation, core steps and cleanup.

apt-get update\napt-get -y install curl ca-certificates\n

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run and be able to download the hello binary from https://seatN.dev.webdav.inmylab.de/hello.

Hint (Click if you are stuck)

Install curl in a before_script to separate the preparation from the core steps:

job_name:\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n

Now place the curl command under script.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: dev\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/100_environments/demo1 -- '*'\n
"},{"location":"100_environments/exercise/#task-2-add-deployment-to-development-environment","title":"Task 2: Add deployment to development environment","text":"

Create a new branch dev from the branch main and modify the job deploy to use the environment from the pre-defined variable $CI_COMMIT_REF_NAME. Mind that the upload URL is also using a hard-coded environment name.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)
stages:\n- check\n- build\n- test\n- deploy\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/100_environments/demo2 -- '*'\n

This was just a demonstration. The changes will not be preseved in the following chapters.

"},{"location":"100_environments/exercise/#task-3-add-deployment-to-production-environment","title":"Task 3: Add deployment to production environment","text":"

Create the branch live from the branch dev and push it without further changes.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run and be able to download the hello binary from https://seatN.live.inmylab.de/hello.

Heads up

Checkout the branch main to make sure that the following exercises are based on the correct code base.

"},{"location":"110_triggers/exercise/","title":"Triggers","text":"

Goal

Learn how to...

  • trigger pipelines in other projects
  • learn about upstream and downstream pipelines
  • use trigger tokens
  • use multi-project pipelines
  • use parent-child pipelines

Heads up

Checkout the branch main to make sure that the following exercises are based on the correct code base.

"},{"location":"110_triggers/exercise/#preparation","title":"Preparation","text":"

Triggering another pipeline requires a seconds project:

  1. Create a new project, e.g. a private project called trigger
  2. Add .gitlab-ci.yml with the following content to the root of new project:
    test:\n  script:\n  - printenv\n
"},{"location":"110_triggers/exercise/#task-1-using-a-trigger-token","title":"Task 1: Using a trigger token","text":"

The trigger token allows pipelines to be triggered using the API. Let's give this a try!

In the web UI:

  1. In the second project, go to Settings > CI/CD and unfold Pipeline trigger tokens
  2. Create a trigger token and copy the token as well as the curl snippet
  3. Go back to demo project
  4. Create an unprotected but masked CI variable called TOKEN

In your pipeline:

  1. Add new stage trigger as well as a job trigger
  2. Add curl snippet in script block
  3. Replace TOKEN with the variable $TOKEN
  4. Replace REF_NAME with branch name (main)

Afterwards check the pipeline in both projects in the GitLab UI. You should see successful pipeline runs.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://${CI_COMMIT_REF_NAME}.seat${SEAT_INDEX}.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  script: |\n    curl https://gitlab.inmylab.de/api/v4/projects/seat${SEAT_INDEX}%2ftrigger/trigger/pipeline \\\n        --request POST \\\n        --silent \\\n        --fail \\\n        -F \"token=${TOKEN}\" \\\n        -F \"ref=main\"\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/110_triggers/curl -- '*'\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"110_triggers/exercise/#task-2-using-a-multi-project-pipeline","title":"Task 2: Using a multi-project pipeline","text":"

The second option for triggering a pipeline in another project, are multi-project pipelines. They come with a handy syntax in gitlab-ci.yaml by using the trigger keyword.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run and be able to expand the downstream pipeline to see the jobs and their status.

Hint (Click if you are stuck)

Replace the script keyword with the trigger keyword.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://${CI_COMMIT_REF_NAME}.seat${SEAT_INDEX}.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger: <path-to-project>\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"110_triggers/exercise/#task-3-using-a-parent-child-pipeline","title":"Task 3: Using a parent-child pipeline","text":"

A parent-child pipeline executes a downstream pipeline from a YAML file. Modify the contents of the trigger keyword to use include to execute a pipeline with the same content as in the first task but - this time - from a local file child.yaml.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run and be able to expand the downstream pipeline to see the jobs and their status.

Hint (Click if you are stuck)

Create the file child.yaml with the following pipeline:

test:\n  script:\n  - printenv\n

Use trigger > include to call the pipeline from this file.

Solution (Click if you are stuck)

child.yaml:

test:\n  script:\n  - printenv\n

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://${CI_COMMIT_REF_NAME}.seat${SEAT_INDEX}.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/110_triggers/parent-child -- '*'\n
"},{"location":"120_templates/exercise/","title":"Templates","text":"

Goal

Learn how to...

  • create templates
  • make jobs reusable
  • load templates from different locations
"},{"location":"120_templates/exercise/#task-1-create-a-template-inline","title":"Task 1: Create a template inline","text":"

Create a template for compiling a go binary from the job build and use it in the job build. See the official documentation for templates for guidance.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

XXX

.build-go:\n  script:\n  #...\n\nbuild:\n  extends: #...\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\n.build-go:\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

You decide whether artifacts is part of the template or not!

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/120_templates/inline -- '*'\n
"},{"location":"120_templates/exercise/#task-2-loading-templates-from-a-local-file","title":"Task 2: Loading templates from a local file","text":"

Move the template into a separate file go.yaml and use the include keyword to import the template.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

go.yaml:

.build-go:\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n
Solution (Click if you are stuck)

go.yaml:

.build-go:\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n

.gitlab-ci.yml:

include:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/120_templates/local -- '*'\n
"},{"location":"120_templates/exercise/#task-3-loading-templates-from-another-project","title":"Task 3: Loading templates from another project","text":"

Create a new project anywhere (!), move go.yaml there and fix the include keyword. See the extended syntax of the include keyword to import templates from another project.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

.gitlab-ci.yml:

include:\n- project: seat/template-go\n  ref: main\n  file: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml --format testname\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: dev\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"130_rules/exercise/","title":"Rules","text":"

Goal

Learn how to...

  • define when to run jobs (and when not)
  • how (workflow) rules can apply to whole pipelines
  • how to use GitLab Pages to publish static web pages

In this exercise we will publish a static web page to download the hello binary.

"},{"location":"130_rules/exercise/#preparation","title":"Preparation","text":"

Add a file public/index.html to your project using the following command:

git checkout origin/160_gitlab_ci/130_rules -- 'public/index.html'\n
"},{"location":"130_rules/exercise/#task-1-prevent-a-job-from-running","title":"Task 1: Prevent a job from running","text":"

Add a job pages to the stage deploy with the following content:

pages:\n  stage: deploy\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n

Review the official documentation for the rules keyword to limit the job pages to run when...

  • the pipeline was triggered by a push event
  • the change applied to the default branch

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint 1 (Click if you are stuck)

In pre-defined variables see $CI_PIPELINE_SOURCE for trigger events, $CI_COMMIT_REF_NAME for the current Git reference and $CI_DEFAULT_BRANCH for the default branch.

Hint 2 (Click if you are stuck)

See complex rules for combining conditions using and (&&) and or (||).

Solution (Click if you are stuck)

.gitlab-ci.yml:

include:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  image: alpine\n  script:\n  - cp hello public\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/130_templates -- '*'\n
"},{"location":"130_rules/exercise/#task-2-prevent-a-pipeline-from-running","title":"Task 2: Prevent a pipeline from running","text":"

Rules can also be placed under the global workflow keyword to apply to the whole pipeline instead of individual jobs.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

The syntax of workflow looks like this:

workflow:\n  rules:\n  #...\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: dev\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  image: alpine\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/130_rules_workflow -- '*'\n
"},{"location":"130_rules/exercise/#task-3-use-deploy-freeze","title":"Task 3: Use deploy freeze","text":"

Projects can define a deploy freeze to prevent pipelines to run but the settings only results in an environment varialbe $CI_DEPLOY_FREEZE. Rules as well as workflow rules can be used to enforce deploy freezes.

Modify the pipeline to prevent the execution when $CI_DEPLOY_FREEZE is not empty.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint 1 (Click if you are stuck)

Simply enter the variable into a rule to check if it is not empty.

Hint 2 (Click if you are stuck)

Checkout the when keyword under if to control whether to start a pipeline/job or not.

Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: dev\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  image: alpine\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"140_merge_requests/exercise/","title":"Merge requests","text":"

Goal

Learn how to...

  • run pipelines in the context of a merge request using rules
  • use template to avoid repetition when using rules
"},{"location":"140_merge_requests/exercise/#task-1-use-rules-to-run-in-merge-request-context","title":"Task 1: Use rules to run in merge request context","text":"

In the last chapter about rules, you learned how to use $CI_PIPELINE_SOURCE to restrict execution to specific events. You will need this now.

On the branch main, add rules to the jobs to specify when to run them:

  1. For the jobs lint, audit, unit_tests, build and test, add rules so that the jobs are executed when...
    1. pushing to the default branch
    2. running in merge request context
  2. Run the job trigger only when pushing to the default branch
  3. Do not modify the existing rules for the job pages

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

$CI_PIPELINE_SOURCE can take the values push and merge_request_event in this context. $CI_COMMIT_REF_NAME contains the name of the Git reference (e.g. branch) the pipeline is running on. $CI_DEFAULT_BRANCH contains the name of the default branch of the repository in the current project. You can use the logical operator && to combine multiple conditions.

Solution (Click if you are stuck)
workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  image: alpine\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/140_merge_requests -- '*'\n
"},{"location":"140_merge_requests/exercise/#task-2-create-a-merge-request","title":"Task 2: Create a merge request","text":"

Now we want to check which jobs are executed in the context of a merge request:

  1. Create a new branch based on main
  2. Create a merge request into main

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

"},{"location":"140_merge_requests/exercise/#bonus-explore-additional-predefined-variables","title":"Bonus: Explore additional predefined variables","text":"

On the branch of the merge request, add a job and run printenv to get a list of variables available to the pipeline. Check out additional variables specific to merge request pipelines. See also the official documentation.

"},{"location":"140_merge_requests/exercise/#task-3-avoid-repetition-using-rule-templates","title":"Task 3: Avoid repetition using rule templates","text":"

In the first task we have implemented the same set of rules for multiple jobs. By combining rules with templates, this repetition can be avoided.

  1. Create an inline template called .run-on-push-to-default with the corresponding rule(s)
  2. Create a second inline template called .run-on-push-and-mr with the corresponding rule(s)
  3. Modify the jobs to use the rule templates

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Use extends to use the rule template in a job.

Remember that only variables are kumulative. All other keywords overwrite each other in the order of appearance.

Solution (Click if you are stuck)
workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/140_merge_requests_rule_templates -- '*'\n
"},{"location":"150_matrix_jobs/exercise/","title":"Matrix Jobs","text":"

Goal

Learn how to...

  • run the same script for different inputs
"},{"location":"150_matrix_jobs/exercise/#preparation","title":"Preparation","text":"

Switch back to the branch main.

"},{"location":"150_matrix_jobs/exercise/#task-1-build-binary-for-multiple-platforms","title":"Task 1: Build binary for multiple platforms","text":"

Build the hello binary for multiple target platform. Matrix jobs enable you to reuse the existing code and parameterize it using different inputs.

Improve the template .build-go in go.yaml to build for linux/amd64 and linux/arm64:

  1. Check the official documentation for parallel to create a matrix job
  2. Add one input for GOOS=linux and GOARCH=amd64
  3. Add another input for GOOS=linux and GOARCH=arm64
  4. Modify the build command to write to separate files using hello-${GOOS}-${GOARCH}
  5. Move the artifact definition into the template and include all hello binaries
  6. Fix the smoke test to execute the hello binary for linux/amd64
  7. Fix the job deploy to upload the hello binary for linux/amd64
  8. Fix the job pages to copy the hello binary for linux/amd64

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

go.yaml:

.build-go:\n  parallel:\n    matrix:\n    - GOOS: linux\n      GOARCH: amd64\n    - GOOS: linux\n      GOARCH: arm64\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello-${GOOS}-${GOARCH} \\\n        .\n  artifacts:\n    paths:\n    - hello-${GOOS}-${GOARCH}\n

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  image: alpine\n  script:\n  - ./hello-linux-amd64\n\ndeploy:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/150_matrix_jobs_demo1 -- '*'\n
"},{"location":"150_matrix_jobs/exercise/#task-2-test-an-alternative-to-specify-the-same-inputs","title":"Task 2: Test an alternative to specify the same inputs","text":"

Test whether the following syntax for the inputs produces the same results in a pipeline:

.build-go:\n  parallel:\n    matrix:\n    - GOOS: linux\n      GOARCH: [ amd64, arm64 ]\n
"},{"location":"150_matrix_jobs/exercise/#bonus-check-binaries-for-correct-platform","title":"Bonus: Check binaries for correct platform","text":"

Add another matrix job to check the target platform of the hello binaries:

  1. Add another template called .test-go to go.yaml defining a matrix job using the same inputs as for .build-go
  2. In the new template run file hello-${GOOS}-${GOARCH} to display the target platform
  3. Modify the job test to use the new template

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

go.yaml:

.go-targets:\n  parallel:\n    matrix:\n    - GOOS: linux\n      GOARCH: amd64\n    - GOOS: linux\n      GOARCH: arm64\n\n.build-go:\n  extends:\n  - .go-targets\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello-${GOOS}-${GOARCH} \\\n        .\n  artifacts:\n    paths:\n    - hello-${GOOS}-${GOARCH}\n\n.test-go:\n  extends:\n  - .go-targets\n  before_script:\n  - apt-get update\n  - apt-get -y install file\n  script:\n  - |\n    file hello-${GOOS}-${GOARCH}\n

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/150_matrix_jobs_demo2 -- '*'\n
"},{"location":"220_services/exercise/","title":"Services","text":"

Goal

Learn how to...

  • define a service
  • access the service
"},{"location":"220_services/exercise/#task-1-create-and-use-a-service","title":"Task 1: Create and use a service","text":"

Service are launched in parallel to the regular job to add missing functionality, e.g. a database backend to execute integration tests. See the official documentation and modify the pipeline:

  1. Create service for the whole pipeline based on the container image nginx:1.20.2
  2. Add a new job test-service to the stage test with the following code:
    curl -s http://nginx\n
  3. Make sure the new job only executes when pushing to main

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

The service is defined globally in the gitlab-ci.yml using the keyword services:

services:\n- nginx:1.20.2\n
Solution (Click if you are stuck)

gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nservices:\n- nginx:1.20.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ntest-service:\n  stage: test\n  extends:\n  - .run-on-push-to-default-branch\n  script:\n  - curl -s http://nginx\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/220_services -- '*'\n
"},{"location":"220_services/exercise/#task-2-move-the-service-into-the-job","title":"Task 2: Move the service into the job","text":"

The above task forced a second container to be created for every job although only one job has used the service. Optimize resource usage by moving the service into the job test-service.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ntest-service:\n  stage: test\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - nginx:1.20.2\n  script:\n  - curl -s http://nginx\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"230_docker/exercise/","title":"Docker","text":"

Goal

Learn how to...

  • build a container image...
  • ...using a service
"},{"location":"230_docker/exercise/#preparation","title":"Preparation","text":"

Building a container image requires a Dockerfile which can be fetched with the following command:

git checkout origin/160_gitlab_ci/230_docker -- Dockerfile\n
"},{"location":"230_docker/exercise/#task-build-a-container-image","title":"Task: Build a container image","text":"

For building a container image, you will need to...

  1. Add a new stage package to the pipeline
  2. Add a new job package to the pipeline
  3. Use the image docker:20.10.18 for the job
  4. Add a rule to limit execution to pushes to the default branch
  5. Add a service to the job:
    1. using the image docker:20.10.18-dind
    2. using the command set to [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]
  6. Add a job variable DOCKER_HOST with value tcp://docker:2375
  7. Execute the command docker build --tag hello .

Heads-Up

The GitLab runner must be configured to run services in privileged mode so that the Docker daemon is able to start.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

The service should be set to:

services:\n- name: docker:20.10.18-dind\n  command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  script:\n  - docker build --tag hello .\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/230_docker -- '*'\n
"},{"location":"240_registries/exercise/","title":"Registries","text":"

Goal

Learn how to...

  • authenticate to the GitLab container registry
  • push a container image to the registry
"},{"location":"240_registries/exercise/#task-push-a-container-image","title":"Task: Push a container image","text":"

GitLab include a container registry. In this task you will push a container image to the registry. If the container registry is enabled, GitLab automatically provides predefined variables to access and authenticate to the registry:

  • $CI_REGISTRY: The registry URL
  • $CI_REGISTRY_IMAGE: The registry URL with the project name
  • $CI_REGISTRY_USER: The username to use to push
  • $CI_REGISTRY_PASSWORD: The password to use to push

Modify the job package:

  1. Update the build command to use the variables above: docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .
  2. Add the push command directly after the build command: docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"
  3. Login to the registry before building: docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"
  4. Logout from the registry after pushing: docker logout \"${CI_REGISTRY}\"

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Use before_script for logging in and after_script after logging out.

Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  before_script:\n  - docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"\n  script:\n  - docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .\n  - docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"\n  after_script:\n  - docker logout \"${CI_REGISTRY}\"\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/240_registries -- '*'\n
"},{"location":"250_releases/exercise/","title":"Releases","text":"

Goal

Learn how to...

  • create a release on GitLab
  • use the GitLab release-cli
"},{"location":"250_releases/exercise/#task-create-a-release","title":"Task: Create a release","text":"

GitLab can create releases based on a Git tag. The release can contain a description and links to assets. The assets must be stored elsewhere.

  1. Check out the official documentation about the release keyword
  2. Modify the job pages to create a release in addition to the script block
  3. The release should be based on the current commit hash ($CI_COMMIT_SHA)
  4. Use the unique pipeline ID ($CI_PIPELINE_IID) as the tag name
  5. Set an arbitrary name and description

For the release keyword to work, the release-cli binary must be present in the execution environment of the job:

  1. Set image to registry.gitlab.com/gitlab-org/release-cli:v0.14.0

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Your release should look similar to this:

release:\n  tag_name: ${CI_PIPELINE_IID}\n  name: Release ${CI_PIPELINE_IID}\n  description: |\n    Some multi\n    line text\n  ref: ${CI_COMMIT_SHA}\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: registry.gitlab.com/gitlab-org/release-cli:v0.14.0\n  release:\n    tag_name: ${CI_PIPELINE_IID}\n    name: Release ${CI_PIPELINE_IID}\n    description: |\n      Some multi\n      line text\n    ref: ${CI_COMMIT_SHA}\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  before_script:\n  - docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"\n  script:\n  - docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .\n  - docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"\n  after_script:\n  - docker logout \"${CI_REGISTRY}\"\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/250_releases -- '*'\n
"},{"location":"265_caches/exercise/","title":"Caches","text":"

Goal

Learn how to...

  • define caches
  • store data in a cache
  • restore data from a cache
  • avoid relying on the cache
"},{"location":"265_caches/exercise/#task-1-test-caching","title":"Task 1: Test caching","text":"

The cache is used by adding the keyword cache in a pipeline job, a job template or in the default section. Let's give it a try:

  1. Add a job test_cache to the first stage
  2. Download dependency information from the uniget project:
    curl -sSLfO https://github.com/uniget-org/cli/raw/main/go.mod\ncurl -sSLfO https://github.com/uniget-org/cli/raw/main/go.sum\n
  3. Use the official example for Go to enable caching
  4. Instead of go test run go mod download to download dependencies only

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run. Retry the job to see the effect of the cache.

Hint (Click if you are stuck)

Use the .go-cache job template from the official example for Go.

Solution (Click if you are stuck)

.gitlab-ci-yml:

#...\n\n.go-cache:\n  variables:\n    GOPATH: $CI_PROJECT_DIR/.go\n  before_script:\n  - mkdir -p .go\n  cache:\n    key: ${CI_PROJECT_PATH_SLUG}\n    policy: pull-push\n    paths:\n    - .go/pkg/mod/\n\ntest_cache:\n  stage: check\n  extends:\n  - .go-cache\n  script:\n  - go mod download  \n\n#...\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"265_caches/exercise/#task-2-add-caching","title":"Task 2: Add caching","text":"

Now, integrate the job template .go-cache into the pipeline and use it for the jobs build and unit_test.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

go.yaml:

.go-targets:\n  parallel:\n    matrix:\n    - GOOS: linux\n      GOARCH: amd64\n    - GOOS: linux\n      GOARCH: arm64\n\n.go-cache:\n  variables:\n    GOPATH: $CI_PROJECT_DIR/.go\n  before_script:\n  - mkdir -p .go\n  cache:\n    key: ${CI_PROJECT_PATH_SLUG}\n    policy: pull-push\n    paths:\n    - .go/pkg/mod/\n\n.build-go:\n  extends:\n  - .go-targets\n  - .go-cache\n  script:\n  - |\n    go build \\\n        -o hello-${GOOS}-${GOARCH} \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        .\n  artifacts:\n    paths:\n    - hello-${GOOS}-${GOARCH}\n\n.test-go:\n  extends:\n  - .go-targets\n  before_script:\n  - apt-get update\n  - apt-get -y install file\n  script:\n  - |\n    file hello-${GOOS}-${GOARCH}\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/265_caches -- '*'\n
"},{"location":"270_renovate/exercise/","title":"Renovate","text":"

Goal

Learn how to...

  • discover dependencies used in your code and pipeline
  • get update proposals for outdated dependencies

We will be using Renovate to discover and update dependencies.

"},{"location":"270_renovate/exercise/#task-add-renovate-to-your-pipeline","title":"Task: Add Renovate to your pipeline","text":"

The easiest way to get Renovate on GitLab is to integrated it into your pipeline:

  1. Create a new project access token renovate with role Developer and scopes api, read_repository, read_registry
  2. Add project access token renovate to CI variable RENOVATE_TOKEN
  3. Add a job renovate to the stage check
  4. Limit execution to a) scheduled pipelines and b) if the variable $RENOVATE is set
  5. Use the image renovate/renovate
  6. Set the variable LOG_LEVEL to debug
  7. Use the following script to execute Renovate:
    renovate --platform gitlab \\\n    --endpoint ${CI_API_V4_URL} \\\n    --token ${RENOVATE_TOKEN} \\\n    ${CI_PROJECT_PATH}\n
  8. Create a scheduled pipeline and define a variable RENOVATE with the value true

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nrenovate:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"schedule\" && $RENOVATE'\n  image: renovate/renovate\n  variables:\n    LOG_LEVEL: debug\n  script: |\n    renovate --platform gitlab \\\n        --endpoint ${CI_API_V4_URL} \\\n        --token ${CI_JOB_TOKEN} \\\n        --autodiscover true\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: registry.gitlab.com/gitlab-org/release-cli:v0.14.0\n  release:\n    tag_name: ${CI_PIPELINE_IID}\n    name: Release ${CI_PIPELINE_IID}\n    description: |\n      Some multi\n      line text\n    ref: ${CI_COMMIT_SHA}\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  before_script:\n  - docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"\n  script:\n  - docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .\n  - docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"\n  after_script:\n  - docker logout \"${CI_REGISTRY}\"\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/270_renovate -- '*'\n
"},{"location":"280_security/exercise/","title":"Security","text":"

Goal

Learn how to...

  • detect secrets in your code
  • detect vulnerabilities in your code
"},{"location":"280_security/exercise/#task-add-integrated-security-scans","title":"Task: Add integrated security scans","text":"

GitLab offers multiple security scanners in the community edition:

  1. Checkout the official documentation for secret detection and integrate it into your pipeline
  2. Checkout the official documentation for static application security testing and integrate it into your pipeline
  3. Checkout the official documentation for container scanning and integrate it into your pipeline

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

The following templates are available for the above features:

  • Secret detection: Security/Secret-Detection.gitlab-ci.yml
  • Static application security testing: Security/SAST.gitlab-ci.yml
  • Container scanning: Security/Container-Scanning.gitlab-ci.yml
Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n- template: Security/Secret-Detection.gitlab-ci.yml\n- template: Security/SAST.gitlab-ci.yml\n- template: Security/Container-Scanning.gitlab-ci.yml\n\ncontainer_scanning:\n  stage: trigger\n  variables:\n    CS_DEFAULT_BRANCH_IMAGE: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\n    CI_APPLICATION_REPOSITORY: ${CI_REGISTRY_IMAGE}\n    CI_APPLICATION_TAG: ${CI_COMMIT_REF_NAME}\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nrenovate:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"schedule\" && $RENOVATE'\n  image: renovate/renovate\n  variables:\n    LOG_LEVEL: debug\n  script: |\n    renovate --platform gitlab \\\n        --endpoint ${CI_API_V4_URL} \\\n        --token ${RENOVATE_TOKEN} \\\n        --autodiscover true\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: registry.gitlab.com/gitlab-org/release-cli:v0.14.0\n  release:\n    tag_name: ${CI_PIPELINE_IID}\n    name: Release ${CI_PIPELINE_IID}\n    description: |\n      Some multi\n      line text\n    ref: ${CI_COMMIT_SHA}\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  before_script:\n  - docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"\n  script:\n  - docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .\n  - docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"\n  after_script:\n  - docker logout \"${CI_REGISTRY}\"\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout origin/160_gitlab_ci/280_security -- '*'\n

Heads-Up

You can also select a different scanner for container scanning using the variable $CS_ANALYZER_IMAGE. The following values are available:

Scanner Image Default (trivy) registry.gitlab.com/security-products/container-scanning:6 Grype registry.gitlab.com/security-products/container-scanning/grype:6 Trivy registry.gitlab.com/security-products/container-scanning/trivy:6"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Workshop: GitLab CI","text":"

This site contains the exercises to learn about GitLab CI.

"},{"location":"#audience","title":"Audience","text":"

Software developers and system administrators who want to learn how to use GitLab CI.

"},{"location":"#duration","title":"Duration","text":"

Two days

"},{"location":"#expected-knowledge","title":"Expected knowledge","text":"

Basic under standing of another CI/CD server, e.g. GitHub and Jenkins

"},{"location":"#goals","title":"Goals","text":"

Learn about the features of GitLab CI and how to use then in a software development project.

"},{"location":"#style","title":"Style","text":"

20% theoretical introduction

80% practical exercises

The instructor will provide an introduction to each topic. Participants will then work on exercises. The instructor will be available to answer questions and provide hints. The instructor will also demonstrate solutions to the exercises.

"},{"location":"navigation/","title":"Navigation","text":"

All pages have the same navigation structure.

"},{"location":"navigation/#section-1-menu-on-the-left","title":"Section 1: Menu on the left","text":"

The left-hand menu contains the available chapters and displays the structure of the workshop.

On smaller screens or windows, the left-hand menu can be shown by clicking on the burger menu .

"},{"location":"navigation/#section-2-page-contents-on-the-right","title":"Section 2: Page contents on the right","text":"

The right-hand menu contains the contents of the current chapter and lists all exercises contained in the current chapter.

On smaller screens or windows, the right-hand menu can be shown by clicking on the burger menu and then clicking on the icon next to the current chapter.

"},{"location":"you_are_done/","title":"You are done!","text":""},{"location":"you_are_done/#congratulations","title":"Congratulations!","text":"

You have finished the workshop

"},{"location":"you_are_ready/","title":"You are ready!","text":"

A few last words...

"},{"location":"you_are_ready/#exercises-tell-a-story","title":"Exercises tell a story","text":"

Each chapter focuses on a single feature

Each exercise improves the previous state

All exercises will have leave some questions unanswered

Following exercises will again improve

"},{"location":"you_are_ready/#work-at-your-own-pace","title":"Work at your own pace","text":"

Either follow topics and exercises at the instructor's pace

Or work ahead if you finish early

Please be mindful of other participants

Please do not confuse participants with your questions when racing ahead

"},{"location":"000_rollout/exercise_gitlab/","title":"GitLab","text":"

This workshop is performed on a shared GitLab instance. You have been assigned a user and password for this instance in an email.

"},{"location":"000_rollout/exercise_gitlab/#task-1-retrieve-your-credentials-digitally","title":"Task 1: Retrieve your credentials digitally","text":"

You have received your credentials in an email but the password is not fun to type in. Let's retrieve it digitally:

  1. Go to https://code.inmylab.de
  2. Select your username from the list
  3. Login using your personal user seatN (where N is a number) and your code (looks like this ABCDEF)
  4. The web page displays your credentials

Keep the page open to refer to your credentials anytime throughout the workshop.

"},{"location":"000_rollout/exercise_gitlab/#task-2-login-to-gitlab","title":"Task 2: Login to GitLab","text":"

Go to https://gitlab.inmylab.de and login with your credentials.

Hint (Click if you are stuck)

Your username looks like seatN where N is a number.

Your password is a long, random string which is displayed on the web pages access in the previous task.

"},{"location":"000_rollout/exercise_gitlab/#task-3-access-the-demo-project","title":"Task 3: Access the demo project","text":"

A personal project has been provisioned for you to follow this workshop. It is called demo. Let's find it!

Hint (Click if you are stuck)

Personal projects are access by clicking on your avatar in the top left corner.

Solution (Click if you are stuck)

The deep link is https://gitlab.inmylab.de/seatN/demo where N is a number.

"},{"location":"000_rollout/exercise_ide/","title":"IDE","text":"

You can follow this workshop in the IDE (Integrated Development Environment) of your choice but...

A web-based instance of Visual Studio Code has been provisioned for you to follow this workshop.

"},{"location":"000_rollout/exercise_ide/#task-access-your-instance-of-visual-studio-code","title":"Task: Access your instance of Visual Studio Code","text":"
  1. Go to https://seatN.vscode.inmylab.de where N is a number.
  2. Login using your personal user seatN (where N is a number) and your password

The password is the same as your GitLab password.

Hint (Click if you are stuck)

Your username looks like seatN where N is a number.

Your password is a long, random string which is displayed on the web pages access in the previous chapter.

"},{"location":"000_rollout/exercise_project/","title":"Project","text":""},{"location":"000_rollout/exercise_project/#task-1-view-the-demo-project","title":"Task 1: View the demo project","text":"

Your instance of Visual Studio Code is provisioned with your credentials to allow Git-over-HTTP to the GitLab instance as well as a clone of the demo project.

  1. In the directory tree
  2. Find the demo project under /home/seat
"},{"location":"000_rollout/exercise_project/#task-2-pulling-from-the-upstream-repository","title":"Task 2: Pulling from the upstream repository","text":"

The local clone of the demo project is configured with a remote pointing to the location of the original project on GitHub. This is prepared in case the instructor needs to fix a demo. Let's test this safeguard:

  1. Open a terminal
  2. Change to the demo project directory
  3. List remotes: git remote -v
  4. Pull from the upstream repository: git pull upstream
"},{"location":"010_jobs_and_stages/exercise/","title":"Jobs and stages","text":"

Goal

Learn how to...

  • create jobs
  • organize them in stages
  • understand when jobs in different stages are executed
"},{"location":"010_jobs_and_stages/exercise/#preparation","title":"Preparation","text":"

This workshop is based on an example hello world application written in Go. Get the code using the following command:

git checkout upstream/160_gitlab_ci/example_app -- '*'\n
"},{"location":"010_jobs_and_stages/exercise/#task-1-create-a-single-job","title":"Task 1: Create a single job","text":"

Add a pipeline to build the code using the following commands:

apk update\napk add go\ngo build -o hello .\n./hello\n

See the official documentation about jobs.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Add a file called .gitlab-ci.yml in the root of the project
  2. Add a job called build
Solution (Click if you are stuck)

.gitlab-ci.yml:

build:\n  script:\n  - apk update\n  - apk add go\n  - go build -o hello .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/010_jobs_and_stages/build -- '*'\n
"},{"location":"010_jobs_and_stages/exercise/#task-2-add-a-stage","title":"Task 2: Add a stage","text":"

Modify the pipeline to consist of two stages called check and build where the check stage contains the following commands:

apk update\napk add go\ngo fmt .\ngo vet .\n

See the official documentation about stages.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Define two stages using stages
  2. Add a job called check in the stage check
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - apk update\n  - apk add go\n  - go build -o hello .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/010_jobs_and_stages/lint -- '*'\n
"},{"location":"010_jobs_and_stages/exercise/#task-3-add-parallel-jobs","title":"Task 3: Add parallel jobs","text":"

Split the job check so that one job called lint executes go fmt . and another job called audit executes go vet ..

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Both jobs lint and audit must be in the stage check.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - apk update\n  - apk add go\n  - go build -o hello .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/010_jobs_and_stages/parallel -- '*'\n
"},{"location":"020_variables/exercise/","title":"Variables","text":"

Goal

Learn how to...

  • add local variable to your pipeline
  • consume pre-defined variables
  • add secrets in the UI
"},{"location":"020_variables/exercise/#task-1-create-a-job-variable","title":"Task 1: Create a job variable","text":"

This exercise requires an updates version of our hello world program:

git checkout upstream/160_gitlab_ci/020_variables/inline -- main.go\n

Add a variable called version to the job called build and modify the build command as follows:

go build -o hello -ldflags \"-X main.Version=${version}\" .\n

See the official documentation about variables.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Use the variable keyword to define a variable inside the job called build
  2. Replace the build command with the one provided above
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go vet .\n\nbuild:\n  stage: build\n  variables:\n    version: dev\n  script:\n  - apk update\n  - apk add go\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${version}\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/020_variables/inline -- '*'

"},{"location":"020_variables/exercise/#task-2-use-a-predefined-variable","title":"Task 2: Use a predefined variable","text":"

Read the official documentation about predefined variables and replace the job variable with the predefined variable CI_COMMIT_REF_NAME.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Remove the variable keyword from the job called build
  2. Replace the variable ${version} with the predefined variable ${CI_COMMIT_REF_NAME}
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - apk update\n  - apk add go\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME}\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/020_variables/predefined -- '*'

"},{"location":"020_variables/exercise/#task-3-add-a-ci-variable-in-the-ui","title":"Task 3: Add a CI variable in the UI","text":"

This exercise requires an updates version of our hello world application:

git checkout upstream/160_gitlab_ci/020_variables/ci -- main.go\n

The application now also prints the name of the author which must be supplied during compilation as well.

Read the official documentation about CI variables and extend the build command to provide main.Author through a CI variable called AUTHOR.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint 1 (Click if you are stuck)
  1. Go to Settings > CI/CD > Variables
  2. Add a variable called AUTHOR with your name
Hint 2 (Click if you are stuck)

The -ldflags option needs to be extended with -X 'main.Author=${AUTHOR}'

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - apk update\n  - apk add go\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - apk update\n  - apk add go\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/020_variables/ci -- '*'\n
"},{"location":"030_script_blocks/exercise/","title":"Scriptblocks","text":"

Goal

Learn how to...

  • Use before_script and after_script
  • Separate preparation and cleanup commands from core functionality
"},{"location":"030_script_blocks/exercise/#task-separate-script-blocks-into-preparation-and-main-task","title":"Task: Separate script blocks into preparation and main task","text":"

Commands are currently specified using the script directive. These commands consist of preparation, core functionality and (possibly) cleanup.

To improve readability, move the preparation of the execution environment to a before_script. See the official documentation.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Move calls to apk to the before_script.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  before_script:\n  - apk update\n  - apk add go\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  before_script:\n  - apk update\n  - apk add go\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  before_script:\n  - apk update\n  - apk add go\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/030_script_blocks -- '*'\n

Cleanup commands can be move to after_script (official documentation) but we have no use for this in the current example.

"},{"location":"030_script_blocks/exercise/#bonus-1-when-after_script-is-executed","title":"Bonus 1: When after_script is executed","text":"

Add commands to all three script block before_script, script and after_script. Test two scenarios:

  1. The pipeline succeeds
  2. The pipeline failes

What happens to the code in after_script?

Solution (Click to reveal)

Command in after_script are always executed even if the job fails.

This can be very useful for cleaning up.

"},{"location":"030_script_blocks/exercise/#bonus-2-what-happens-to-environment-variables-in-script-blocks","title":"Bonus 2: What happens to environment variables in script blocks?","text":"

Define environment variables in all three script blocks and display them in the same and in the following script block.

When are environment variables available?

Solution (Click to reveal)

Commands in before_script and script share a shell session. Environment variables are available throughout these script blocks.

Commands in after_script are executed in a new shell session. Environment variables defined in before_script and script are gone.

"},{"location":"040_image/exercise/","title":"Images","text":"

Goal

Learn how to...

  • specify which container image to use for a job
  • tailor the execution environment to your needs
"},{"location":"040_image/exercise/#task-simplify-using-container-images","title":"Task: Simplify using container images","text":"

In the previous exampes, we called apk at the beginning of every job to install Go. This had to be repeated for every job because Go was not present. Choosing an image for a job using the image directive, time is saved by avoiding commands to install required tools. See the official documentation.

Replace the calls to apk with the container image golang:1.19.2.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  • Remove before_script
  • Add image: golang:1.19.2 instead
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\nlint:\n  stage: check\n  image: golang:1.19.2\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  image: golang:1.19.2\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  image: golang:1.19.2\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/040_image -- '*'\n
"},{"location":"040_image/exercise/#bonus-test-different-images","title":"Bonus: Test different images","text":"

Add a job to your pipeline to test different container images. Check how different images offer specialized execution environments:

  1. Use python:3 and test running python --version
  2. Use node and test running node --version
"},{"location":"050_defaults/exercise/","title":"Defaults","text":"

Goal

Learn how to...

  • avoid repetition in jobs
  • specify defaults are the top of your pipeline
"},{"location":"050_defaults/exercise/#task-dont-repeat-yourself","title":"Task: Don't repeat yourself","text":"

All jobs currently have a dedicated image directive. Using defaults, this repetition can be avoided. See the official documentation.

Replace job specific image directives with the default directive.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)
  1. Remove image from all build jobs
  2. Add default with the image directive at the top
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/050_default -- '*'\n
"},{"location":"050_defaults/exercise/#bonus-1-override-defaults","title":"Bonus 1: Override defaults","text":"

Jobs can still choose to use an image different from the default:

  1. Add a new job
  2. Add an image directory to the new job
  3. Specify a different image
  4. Check out how the executation environment changes
"},{"location":"050_defaults/exercise/#bonus-2-default-values-for-variables","title":"Bonus 2: Default values for variables","text":"

See the official documentation for default as well as variables and check how they are related.

Solution (Click if you are stuck)

Global variables are not located under default but under the global variables keyword.

"},{"location":"060_artifacts/exercise/","title":"Artifacts","text":"

Goal

Learn how to...

  • define artifacts
  • consume artifacts
"},{"location":"060_artifacts/exercise/#task-1-pass-an-artifact-to-the-next-stage","title":"Task 1: Pass an artifact to the next stage","text":"

Artifacts are useful for splitting a task in separate job. Refer to the official documentation.

Improve the pipeline by using artifacts:

  1. Create an artifact from the hello binary
  2. Create a new stage called test with a job called test
  3. Call the hello binary as a smoke test

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Example for creating an artifacts:

job_name:\n  script:\n  - echo foo >file.txt\n  artifacts:\n    paths:\n    - file.txt\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/060_artifact -- '*'\n
"},{"location":"060_artifacts/exercise/#bonus-1-define-from-which-jobs-to-receive-artifacts","title":"Bonus 1: Define from which jobs to receive artifacts","text":"

Usually, artifacts are received from all jobs in the previous stages. Decide from which jobs to receive artifacts using the dependencies keyword. See the official documentation.

Modify the job test to consume artifacts only from the job build.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  dependencies:\n  - build\n  script:\n  - ./hello\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"060_artifacts/exercise/#bonus-2-passing-environment-variables","title":"Bonus 2: Passing environment variables","text":"

In some situations, artifacts are to heavy-weight and passing a variable would be enough. Read the documentation for passing environment variables and implement this between two jobs of your choice.

The following hint and solution are a working example.

Hint (Click if you are stuck)

Example for creating an artifact for environment variables:

job_name:\n  script:\n  - echo \"foo=bar\" >build.env\n  artifacts:\n    reports:\n      dotenv: build.env\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  variables:\n    BINARY_NAME: hello\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o \"${BINARY_NAME}\" \\\n        .\n  - echo \"${BINARY_NAME}\" >build.env\n  artifacts:\n    paths:\n    - hello\n    reports:\n      dotenv: build.env\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./${BINARY_NAME}\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"065_job_dependencies/exercise/","title":"Job dependencies","text":"

Goal

Learn how to...

  • ignore stages
  • start jobs as soon as dependencies are met
"},{"location":"065_job_dependencies/exercise/#task-start-a-job-early","title":"Task: Start a job early","text":"

Start the job build as soon as the job audit completes without waiting for other job of the stage check to finish. Check out the official documentation of needs.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  needs:\n  - audit\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"065_job_dependencies/exercise/#bonus-start-a-job-late","title":"Bonus: Start a job late","text":"

If two jobs in the same stage should not be executed at the same time, the needs keyword can also delay a job until the dependencies are met. Modify the job lint so that it waits for the job audit to finish.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  needs:\n  - audit\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  needs:\n  - audit\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"070_schedules/exercise/","title":"Schedules","text":"

Goal

Learn how to...

  • schedule a pipeline run
  • use a schedule to execute a preconfigured pipeline
"},{"location":"070_schedules/exercise/#task-create-a-schedule","title":"Task: Create a schedule","text":"

Create a schedule to run a 5 minutes on the branch main in the correct timezone. See the official documentation for schedules.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

"},{"location":"070_schedules/exercise/#bonus-start-a-schedule-manually","title":"Bonus: Start a schedule manually","text":"

Run the previously created schedule manually by clicking the play button . This come in handy if you need to run a pipeline with pre-configured variables.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

"},{"location":"090_unit_tests/exercise/","title":"Unit tests","text":"

Goal

Learn how to...

  • execute unit tests
  • publish results in GitLab

This exercise adds a unit test to the hello world application.

"},{"location":"090_unit_tests/exercise/#preparation","title":"Preparation","text":"

Let's update the code:

git checkout upstream/160_gitlab_ci/090_unit_tests -- main_test.go go.mod go.sum\n
"},{"location":"090_unit_tests/exercise/#task-publish-unit-test-results","title":"Task: Publish unit test results","text":"

The following commands execute unit tests and automatically convert the results to JUnit using gotestsum:

go install gotest.tools/gotestsum@latest\ngotestsum --junitfile report.xml\n

See the official documentation for special artifacts and specifically reports.

Add a job unit_test to the stage check containing the above commands. The job needs to define a special artifact from the file report.xml so that GitLab recognizes it as as JUnit XML report.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run which shows the unit test results on the tab in the overview.

Hint (Click if you are stuck)

GitLab has published an example. The unit test report is published using a special type of artifact:

build:\n  stage: test\n  script: echo\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/090_unit_tests -- '*'\n
"},{"location":"100_environments/exercise/","title":"Environments","text":"

Goal

Learn how to...

  • use environments to specify deployment targets
  • select environments dynamically
"},{"location":"100_environments/exercise/#preparation","title":"Preparation","text":"

Create CI variables for use in the following exercises:

  1. Retrieve passwords for dev and live environments from the info page
  2. Create unprotected but masked CI variable PASS twice with scope dev and live
  3. Create unprotected CI variable SEAT_INDEX with your seat number
"},{"location":"100_environments/exercise/#task-1-add-target-environment","title":"Task 1: Add target environment","text":"

Add a new stage deploy with a job called deploy and use the following commands to upload the binary to the dev environment:

curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n    --fail \\\n    --verbose \\\n    --upload-file hello \\\n    --user seat${SEAT_INDEX}:${PASS}\n

Mind that curl is not available in the default image golang:1.19.2 but must be installed using the following commands. Apply what you learned about script blocks as well as separating commands into preparation, core steps and cleanup.

apt-get update\napt-get -y install curl ca-certificates\n

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run and be able to download the hello binary from https://seatN.dev.webdav.inmylab.de/hello.

Hint (Click if you are stuck)

Install curl in a before_script to separate the preparation from the core steps:

job_name:\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n

Now place the curl command under script.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: dev\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/100_environments/demo1 -- '*'\n
"},{"location":"100_environments/exercise/#task-2-add-deployment-to-development-environment","title":"Task 2: Add deployment to development environment","text":"

Create a new branch dev from the branch main and modify the job deploy to use the environment from the pre-defined variable $CI_COMMIT_REF_NAME. Mind that the upload URL is also using a hard-coded environment name.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)
stages:\n- check\n- build\n- test\n- deploy\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/100_environments/demo2 -- '*'\n

This was just a demonstration. The changes will not be preseved in the following chapters.

"},{"location":"100_environments/exercise/#task-3-add-deployment-to-production-environment","title":"Task 3: Add deployment to production environment","text":"

Create the branch live from the branch dev and push it without further changes.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run and be able to download the hello binary from https://seatN.live.inmylab.de/hello.

Heads up

Checkout the branch main to make sure that the following exercises are based on the correct code base.

"},{"location":"110_triggers/exercise/","title":"Triggers","text":"

Goal

Learn how to...

  • trigger pipelines in other projects
  • learn about upstream and downstream pipelines
  • use trigger tokens
  • use multi-project pipelines
  • use parent-child pipelines

Heads up

Checkout the branch main to make sure that the following exercises are based on the correct code base.

"},{"location":"110_triggers/exercise/#preparation","title":"Preparation","text":"

Triggering another pipeline requires a seconds project:

  1. Create a new project, e.g. a private project called trigger
  2. Add .gitlab-ci.yml with the following content to the root of new project:
    test:\n  script:\n  - printenv\n
"},{"location":"110_triggers/exercise/#task-1-using-a-trigger-token","title":"Task 1: Using a trigger token","text":"

The trigger token allows pipelines to be triggered using the API. Let's give this a try!

In the web UI:

  1. In the second project, go to Settings > CI/CD and unfold Pipeline trigger tokens
  2. Create a trigger token and copy the token as well as the curl snippet
  3. Go back to demo project
  4. Create an unprotected but masked CI variable called TOKEN

In your pipeline:

  1. Add new stage trigger as well as a job trigger
  2. Add curl snippet in script block
  3. Replace TOKEN with the variable $TOKEN
  4. Replace REF_NAME with branch name (main)

Afterwards check the pipeline in both projects in the GitLab UI. You should see successful pipeline runs.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://${CI_COMMIT_REF_NAME}.seat${SEAT_INDEX}.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  script: |\n    curl https://gitlab.inmylab.de/api/v4/projects/seat${SEAT_INDEX}%2ftrigger/trigger/pipeline \\\n        --request POST \\\n        --silent \\\n        --fail \\\n        -F \"token=${TOKEN}\" \\\n        -F \"ref=main\"\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/110_triggers/curl -- '*'\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"110_triggers/exercise/#task-2-using-a-multi-project-pipeline","title":"Task 2: Using a multi-project pipeline","text":"

The second option for triggering a pipeline in another project, are multi-project pipelines. They come with a handy syntax in gitlab-ci.yaml by using the trigger keyword.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run and be able to expand the downstream pipeline to see the jobs and their status.

Hint (Click if you are stuck)

Replace the script keyword with the trigger keyword.

Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://${CI_COMMIT_REF_NAME}.seat${SEAT_INDEX}.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger: <path-to-project>\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"110_triggers/exercise/#task-3-using-a-parent-child-pipeline","title":"Task 3: Using a parent-child pipeline","text":"

A parent-child pipeline executes a downstream pipeline from a YAML file. Modify the contents of the trigger keyword to use include to execute a pipeline with the same content as in the first task but - this time - from a local file child.yaml.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run and be able to expand the downstream pipeline to see the jobs and their status.

Hint (Click if you are stuck)

Create the file child.yaml with the following pipeline:

test:\n  script:\n  - printenv\n

Use trigger > include to call the pipeline from this file.

Solution (Click if you are stuck)

child.yaml:

test:\n  script:\n  - printenv\n

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://${CI_COMMIT_REF_NAME}.seat${SEAT_INDEX}.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/110_triggers/parent-child -- '*'\n
"},{"location":"120_templates/exercise/","title":"Templates","text":"

Goal

Learn how to...

  • create templates
  • make jobs reusable
  • load templates from different locations
"},{"location":"120_templates/exercise/#task-1-create-a-template-inline","title":"Task 1: Create a template inline","text":"

Create a template for compiling a go binary from the job build and use it in the job build. See the official documentation for templates for guidance.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

XXX

.build-go:\n  script:\n  #...\n\nbuild:\n  extends: #...\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

stages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\n.build-go:\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

You decide whether artifacts is part of the template or not!

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/120_templates/inline -- '*'\n
"},{"location":"120_templates/exercise/#task-2-loading-templates-from-a-local-file","title":"Task 2: Loading templates from a local file","text":"

Move the template into a separate file go.yaml and use the include keyword to import the template.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

go.yaml:

.build-go:\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n
Solution (Click if you are stuck)

go.yaml:

.build-go:\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello \\\n        .\n

.gitlab-ci.yml:

include:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/120_templates/local -- '*'\n
"},{"location":"120_templates/exercise/#task-3-loading-templates-from-another-project","title":"Task 3: Loading templates from another project","text":"

Create a new project anywhere (!), move go.yaml there and fix the include keyword. See the extended syntax of the include keyword to import templates from another project.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

.gitlab-ci.yml:

include:\n- project: seat/template-go\n  ref: main\n  file: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml --format testname\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: dev\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"130_rules/exercise/","title":"Rules","text":"

Goal

Learn how to...

  • define when to run jobs (and when not)
  • how (workflow) rules can apply to whole pipelines
  • how to use GitLab Pages to publish static web pages

In this exercise we will publish a static web page to download the hello binary.

"},{"location":"130_rules/exercise/#preparation","title":"Preparation","text":"

Add a file public/index.html to your project using the following command:

git checkout upstream/160_gitlab_ci/130_rules -- 'public/index.html'\n
"},{"location":"130_rules/exercise/#task-1-prevent-a-job-from-running","title":"Task 1: Prevent a job from running","text":"

Add a job pages to the stage deploy with the following content:

pages:\n  stage: deploy\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n

Review the official documentation for the rules keyword to limit the job pages to run when...

  • the pipeline was triggered by a push event
  • the change applied to the default branch

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint 1 (Click if you are stuck)

In pre-defined variables see $CI_PIPELINE_SOURCE for trigger events, $CI_COMMIT_REF_NAME for the current Git reference and $CI_DEFAULT_BRANCH for the default branch.

Hint 2 (Click if you are stuck)

See complex rules for combining conditions using and (&&) and or (||).

Solution (Click if you are stuck)

.gitlab-ci.yml:

include:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  image: alpine\n  script:\n  - cp hello public\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/130_templates -- '*'\n
"},{"location":"130_rules/exercise/#task-2-prevent-a-pipeline-from-running","title":"Task 2: Prevent a pipeline from running","text":"

Rules can also be placed under the global workflow keyword to apply to the whole pipeline instead of individual jobs.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

The syntax of workflow looks like this:

workflow:\n  rules:\n  #...\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: dev\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  image: alpine\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/130_rules_workflow -- '*'\n
"},{"location":"130_rules/exercise/#task-3-use-deploy-freeze","title":"Task 3: Use deploy freeze","text":"

Projects can define a deploy freeze to prevent pipelines to run but the settings only results in an environment varialbe $CI_DEPLOY_FREEZE. Rules as well as workflow rules can be used to enforce deploy freezes.

Modify the pipeline to prevent the execution when $CI_DEPLOY_FREEZE is not empty.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint 1 (Click if you are stuck)

Simply enter the variable into a rule to check if it is not empty.

Hint 2 (Click if you are stuck)

Checkout the when keyword under if to control whether to start a pipeline/job or not.

Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  environment:\n    name: dev\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.dev.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  image: alpine\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  trigger:\n    include: child.yaml\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"140_merge_requests/exercise/","title":"Merge requests","text":"

Goal

Learn how to...

  • run pipelines in the context of a merge request using rules
  • use template to avoid repetition when using rules
"},{"location":"140_merge_requests/exercise/#task-1-use-rules-to-run-in-merge-request-context","title":"Task 1: Use rules to run in merge request context","text":"

In the last chapter about rules, you learned how to use $CI_PIPELINE_SOURCE to restrict execution to specific events. You will need this now.

On the branch main, add rules to the jobs to specify when to run them:

  1. For the jobs lint, audit, unit_tests, build and test, add rules so that the jobs are executed when...
    1. pushing to the default branch
    2. running in merge request context
  2. Run the job trigger only when pushing to the default branch
  3. Do not modify the existing rules for the job pages

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

$CI_PIPELINE_SOURCE can take the values push and merge_request_event in this context. $CI_COMMIT_REF_NAME contains the name of the Git reference (e.g. branch) the pipeline is running on. $CI_DEFAULT_BRANCH contains the name of the default branch of the repository in the current project. You can use the logical operator && to combine multiple conditions.

Solution (Click if you are stuck)
workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  extends:\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  image: alpine\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/140_merge_requests -- '*'\n
"},{"location":"140_merge_requests/exercise/#task-2-create-a-merge-request","title":"Task 2: Create a merge request","text":"

Now we want to check which jobs are executed in the context of a merge request:

  1. Create a new branch based on main
  2. Create a merge request into main

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

"},{"location":"140_merge_requests/exercise/#bonus-explore-additional-predefined-variables","title":"Bonus: Explore additional predefined variables","text":"

On the branch of the merge request, add a job and run printenv to get a list of variables available to the pipeline. Check out additional variables specific to merge request pipelines. See also the official documentation.

"},{"location":"140_merge_requests/exercise/#task-3-avoid-repetition-using-rule-templates","title":"Task 3: Avoid repetition using rule templates","text":"

In the first task we have implemented the same set of rules for multiple jobs. By combining rules with templates, this repetition can be avoided.

  1. Create an inline template called .run-on-push-to-default with the corresponding rule(s)
  2. Create a second inline template called .run-on-push-and-mr with the corresponding rule(s)
  3. Modify the jobs to use the rule templates

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Use extends to use the rule template in a job.

Remember that only variables are kumulative. All other keywords overwrite each other in the order of appearance.

Solution (Click if you are stuck)
workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n  artifacts:\n    paths:\n    - hello\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  image: alpine\n  script:\n  - ./hello\n\ndeploy:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello public/\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/140_merge_requests_rule_templates -- '*'\n
"},{"location":"150_matrix_jobs/exercise/","title":"Matrix Jobs","text":"

Goal

Learn how to...

  • run the same script for different inputs
"},{"location":"150_matrix_jobs/exercise/#preparation","title":"Preparation","text":"

Switch back to the branch main.

"},{"location":"150_matrix_jobs/exercise/#task-1-build-binary-for-multiple-platforms","title":"Task 1: Build binary for multiple platforms","text":"

Build the hello binary for multiple target platform. Matrix jobs enable you to reuse the existing code and parameterize it using different inputs.

Improve the template .build-go in go.yaml to build for linux/amd64 and linux/arm64:

  1. Check the official documentation for parallel to create a matrix job
  2. Add one input for GOOS=linux and GOARCH=amd64
  3. Add another input for GOOS=linux and GOARCH=arm64
  4. Modify the build command to write to separate files using hello-${GOOS}-${GOARCH}
  5. Move the artifact definition into the template and include all hello binaries
  6. Fix the smoke test to execute the hello binary for linux/amd64
  7. Fix the job deploy to upload the hello binary for linux/amd64
  8. Fix the job pages to copy the hello binary for linux/amd64

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

go.yaml:

.build-go:\n  parallel:\n    matrix:\n    - GOOS: linux\n      GOARCH: amd64\n    - GOOS: linux\n      GOARCH: arm64\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello-${GOOS}-${GOARCH} \\\n        .\n  artifacts:\n    paths:\n    - hello-${GOOS}-${GOARCH}\n

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  image: alpine\n  script:\n  - ./hello-linux-amd64\n\ndeploy:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/150_matrix_jobs_demo1 -- '*'\n
"},{"location":"150_matrix_jobs/exercise/#task-2-test-an-alternative-to-specify-the-same-inputs","title":"Task 2: Test an alternative to specify the same inputs","text":"

Test whether the following syntax for the inputs produces the same results in a pipeline:

.build-go:\n  parallel:\n    matrix:\n    - GOOS: linux\n      GOARCH: [ amd64, arm64 ]\n
"},{"location":"150_matrix_jobs/exercise/#bonus-check-binaries-for-correct-platform","title":"Bonus: Check binaries for correct platform","text":"

Add another matrix job to check the target platform of the hello binaries:

  1. Add another template called .test-go to go.yaml defining a matrix job using the same inputs as for .build-go
  2. In the new template run file hello-${GOOS}-${GOARCH} to display the target platform
  3. Modify the job test to use the new template

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

go.yaml:

.go-targets:\n  parallel:\n    matrix:\n    - GOOS: linux\n      GOARCH: amd64\n    - GOOS: linux\n      GOARCH: arm64\n\n.build-go:\n  extends:\n  - .go-targets\n  script:\n  - |\n    go build \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        -o hello-${GOOS}-${GOARCH} \\\n        .\n  artifacts:\n    paths:\n    - hello-${GOOS}-${GOARCH}\n\n.test-go:\n  extends:\n  - .go-targets\n  before_script:\n  - apt-get update\n  - apt-get -y install file\n  script:\n  - |\n    file hello-${GOOS}-${GOARCH}\n

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/150_matrix_jobs_demo2 -- '*'\n
"},{"location":"220_services/exercise/","title":"Services","text":"

Goal

Learn how to...

  • define a service
  • access the service
"},{"location":"220_services/exercise/#task-1-create-and-use-a-service","title":"Task 1: Create and use a service","text":"

Service are launched in parallel to the regular job to add missing functionality, e.g. a database backend to execute integration tests. See the official documentation and modify the pipeline:

  1. Create service for the whole pipeline based on the container image nginx:1.20.2
  2. Add a new job test-service to the stage test with the following code:
    curl -s http://nginx\n
  3. Make sure the new job only executes when pushing to main

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

The service is defined globally in the gitlab-ci.yml using the keyword services:

services:\n- nginx:1.20.2\n
Solution (Click if you are stuck)

gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nservices:\n- nginx:1.20.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ntest-service:\n  stage: test\n  extends:\n  - .run-on-push-to-default-branch\n  script:\n  - curl -s http://nginx\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/220_services -- '*'\n
"},{"location":"220_services/exercise/#task-2-move-the-service-into-the-job","title":"Task 2: Move the service into the job","text":"

The above task forced a second container to be created for every job although only one job has used the service. Optimize resource usage by moving the service into the job test-service.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ntest-service:\n  stage: test\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - nginx:1.20.2\n  script:\n  - curl -s http://nginx\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"230_docker/exercise/","title":"Docker","text":"

Goal

Learn how to...

  • build a container image...
  • ...using a service
"},{"location":"230_docker/exercise/#preparation","title":"Preparation","text":"

Building a container image requires a Dockerfile which can be fetched with the following command:

git checkout upstream/160_gitlab_ci/230_docker -- Dockerfile\n
"},{"location":"230_docker/exercise/#task-build-a-container-image","title":"Task: Build a container image","text":"

For building a container image, you will need to...

  1. Add a new stage package to the pipeline
  2. Add a new job package to the pipeline
  3. Use the image docker:20.10.18 for the job
  4. Add a rule to limit execution to pushes to the default branch
  5. Add a service to the job:
    1. using the image docker:20.10.18-dind
    2. using the command set to [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]
  6. Add a job variable DOCKER_HOST with value tcp://docker:2375
  7. Execute the command docker build --tag hello .

Heads-Up

The GitLab runner must be configured to run services in privileged mode so that the Docker daemon is able to start.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

The service should be set to:

services:\n- name: docker:20.10.18-dind\n  command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  script:\n  - docker build --tag hello .\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/230_docker -- '*'\n
"},{"location":"240_registries/exercise/","title":"Registries","text":"

Goal

Learn how to...

  • authenticate to the GitLab container registry
  • push a container image to the registry
"},{"location":"240_registries/exercise/#task-push-a-container-image","title":"Task: Push a container image","text":"

GitLab include a container registry. In this task you will push a container image to the registry. If the container registry is enabled, GitLab automatically provides predefined variables to access and authenticate to the registry:

  • $CI_REGISTRY: The registry URL
  • $CI_REGISTRY_IMAGE: The registry URL with the project name
  • $CI_REGISTRY_USER: The username to use to push
  • $CI_REGISTRY_PASSWORD: The password to use to push

Modify the job package:

  1. Update the build command to use the variables above: docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .
  2. Add the push command directly after the build command: docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"
  3. Login to the registry before building: docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"
  4. Logout from the registry after pushing: docker logout \"${CI_REGISTRY}\"

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Use before_script for logging in and after_script after logging out.

Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: alpine\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  before_script:\n  - docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"\n  script:\n  - docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .\n  - docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"\n  after_script:\n  - docker logout \"${CI_REGISTRY}\"\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/240_registries -- '*'\n
"},{"location":"250_releases/exercise/","title":"Releases","text":"

Goal

Learn how to...

  • create a release on GitLab
  • use the GitLab release-cli
"},{"location":"250_releases/exercise/#task-create-a-release","title":"Task: Create a release","text":"

GitLab can create releases based on a Git tag. The release can contain a description and links to assets. The assets must be stored elsewhere.

  1. Check out the official documentation about the release keyword
  2. Modify the job pages to create a release in addition to the script block
  3. The release should be based on the current commit hash ($CI_COMMIT_SHA)
  4. Use the unique pipeline ID ($CI_PIPELINE_IID) as the tag name
  5. Set an arbitrary name and description

For the release keyword to work, the release-cli binary must be present in the execution environment of the job:

  1. Set image to registry.gitlab.com/gitlab-org/release-cli:v0.14.0

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

Your release should look similar to this:

release:\n  tag_name: ${CI_PIPELINE_IID}\n  name: Release ${CI_PIPELINE_IID}\n  description: |\n    Some multi\n    line text\n  ref: ${CI_COMMIT_SHA}\n
Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: registry.gitlab.com/gitlab-org/release-cli:v0.14.0\n  release:\n    tag_name: ${CI_PIPELINE_IID}\n    name: Release ${CI_PIPELINE_IID}\n    description: |\n      Some multi\n      line text\n    ref: ${CI_COMMIT_SHA}\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  before_script:\n  - docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"\n  script:\n  - docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .\n  - docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"\n  after_script:\n  - docker logout \"${CI_REGISTRY}\"\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/250_releases -- '*'\n
"},{"location":"265_caches/exercise/","title":"Caches","text":"

Goal

Learn how to...

  • define caches
  • store data in a cache
  • restore data from a cache
  • avoid relying on the cache
"},{"location":"265_caches/exercise/#task-1-test-caching","title":"Task 1: Test caching","text":"

The cache is used by adding the keyword cache in a pipeline job, a job template or in the default section. Let's give it a try:

  1. Add a job test_cache to the first stage
  2. Download dependency information from the uniget project:
    curl -sSLfO https://github.com/uniget-org/cli/raw/main/go.mod\ncurl -sSLfO https://github.com/uniget-org/cli/raw/main/go.sum\n
  3. Use the official example for Go to enable caching
  4. Instead of go test run go mod download to download dependencies only

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run. Retry the job to see the effect of the cache.

Hint (Click if you are stuck)

Use the .go-cache job template from the official example for Go.

Solution (Click if you are stuck)

.gitlab-ci-yml:

#...\n\n.go-cache:\n  variables:\n    GOPATH: $CI_PROJECT_DIR/.go\n  before_script:\n  - mkdir -p .go\n  cache:\n    key: ${CI_PROJECT_PATH_SLUG}\n    policy: pull-push\n    paths:\n    - .go/pkg/mod/\n\ntest_cache:\n  stage: check\n  extends:\n  - .go-cache\n  script:\n  - go mod download  \n\n#...\n

This was just a demonstration. The changes will not be preserved in the following chapters.

"},{"location":"265_caches/exercise/#task-2-add-caching","title":"Task 2: Add caching","text":"

Now, integrate the job template .go-cache into the pipeline and use it for the jobs build and unit_test.

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

go.yaml:

.go-targets:\n  parallel:\n    matrix:\n    - GOOS: linux\n      GOARCH: amd64\n    - GOOS: linux\n      GOARCH: arm64\n\n.go-cache:\n  variables:\n    GOPATH: $CI_PROJECT_DIR/.go\n  before_script:\n  - mkdir -p .go\n  cache:\n    key: ${CI_PROJECT_PATH_SLUG}\n    policy: pull-push\n    paths:\n    - .go/pkg/mod/\n\n.build-go:\n  extends:\n  - .go-targets\n  - .go-cache\n  script:\n  - |\n    go build \\\n        -o hello-${GOOS}-${GOARCH} \\\n        -ldflags \"-X main.Version=${CI_COMMIT_REF_NAME} -X 'main.Author=${AUTHOR}'\" \\\n        .\n  artifacts:\n    paths:\n    - hello-${GOOS}-${GOARCH}\n\n.test-go:\n  extends:\n  - .go-targets\n  before_script:\n  - apt-get update\n  - apt-get -y install file\n  script:\n  - |\n    file hello-${GOOS}-${GOARCH}\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/265_caches -- '*'\n
"},{"location":"270_renovate/exercise/","title":"Renovate","text":"

Goal

Learn how to...

  • discover dependencies used in your code and pipeline
  • get update proposals for outdated dependencies

We will be using Renovate to discover and update dependencies.

"},{"location":"270_renovate/exercise/#task-add-renovate-to-your-pipeline","title":"Task: Add Renovate to your pipeline","text":"

The easiest way to get Renovate on GitLab is to integrated it into your pipeline:

  1. Create a new project access token renovate with role Developer and scopes api, read_repository, read_registry
  2. Add project access token renovate to CI variable RENOVATE_TOKEN
  3. Add a job renovate to the stage check
  4. Limit execution to a) scheduled pipelines and b) if the variable $RENOVATE is set
  5. Use the image renovate/renovate
  6. Set the variable LOG_LEVEL to debug
  7. Use the following script to execute Renovate:
    renovate --platform gitlab \\\n    --endpoint ${CI_API_V4_URL} \\\n    --token ${RENOVATE_TOKEN} \\\n    ${CI_PROJECT_PATH}\n
  8. Create a scheduled pipeline and define a variable RENOVATE with the value true

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nrenovate:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"schedule\" && $RENOVATE'\n  image: renovate/renovate\n  variables:\n    LOG_LEVEL: debug\n  script: |\n    renovate --platform gitlab \\\n        --endpoint ${CI_API_V4_URL} \\\n        --token ${CI_JOB_TOKEN} \\\n        --autodiscover true\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: registry.gitlab.com/gitlab-org/release-cli:v0.14.0\n  release:\n    tag_name: ${CI_PIPELINE_IID}\n    name: Release ${CI_PIPELINE_IID}\n    description: |\n      Some multi\n      line text\n    ref: ${CI_COMMIT_SHA}\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  before_script:\n  - docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"\n  script:\n  - docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .\n  - docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"\n  after_script:\n  - docker logout \"${CI_REGISTRY}\"\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/270_renovate -- '*'\n
"},{"location":"280_security/exercise/","title":"Security","text":"

Goal

Learn how to...

  • detect secrets in your code
  • detect vulnerabilities in your code
"},{"location":"280_security/exercise/#task-add-integrated-security-scans","title":"Task: Add integrated security scans","text":"

GitLab offers multiple security scanners in the community edition:

  1. Checkout the official documentation for secret detection and integrate it into your pipeline
  2. Checkout the official documentation for static application security testing and integrate it into your pipeline
  3. Checkout the official documentation for container scanning and integrate it into your pipeline

Afterwards check the pipeline in the GitLab UI. You should see a successful pipeline run.

Hint (Click if you are stuck)

The following templates are available for the above features:

  • Secret detection: Security/Secret-Detection.gitlab-ci.yml
  • Static application security testing: Security/SAST.gitlab-ci.yml
  • Container scanning: Security/Container-Scanning.gitlab-ci.yml
Solution (Click if you are stuck)

.gitlab-ci.yml:

workflow:\n  rules:\n  - if: $CI_DEPLOY_FREEZE\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'push'\n  - if: $CI_PIPELINE_SOURCE == 'web'\n  - if: $CI_PIPELINE_SOURCE == 'schedule'\n  - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n  - if: $CI_PIPELINE_SOURCE == 'pipeline'\n  - if: $CI_PIPELINE_SOURCE == 'api'\n    when: never\n  - if: $CI_PIPELINE_SOURCE == 'trigger'\n    when: never\n\ninclude:\n- local: go.yaml\n- template: Security/Secret-Detection.gitlab-ci.yml\n- template: Security/SAST.gitlab-ci.yml\n- template: Security/Container-Scanning.gitlab-ci.yml\n\ncontainer_scanning:\n  stage: trigger\n  variables:\n    CS_DEFAULT_BRANCH_IMAGE: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\n    CI_APPLICATION_REPOSITORY: ${CI_REGISTRY_IMAGE}\n    CI_APPLICATION_TAG: ${CI_COMMIT_REF_NAME}\n\n.run-on-push-to-default-branch:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n\n.run-on-push-and-in-mr:\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"push\" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n  - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n\nstages:\n- check\n- build\n- test\n- deploy\n- package\n- trigger\n\ndefault:\n  image: golang:1.19.2\n\nrenovate:\n  stage: check\n  rules:\n  - if: '$CI_PIPELINE_SOURCE == \"schedule\" && $RENOVATE'\n  image: renovate/renovate\n  variables:\n    LOG_LEVEL: debug\n  script: |\n    renovate --platform gitlab \\\n        --endpoint ${CI_API_V4_URL} \\\n        --token ${RENOVATE_TOKEN} \\\n        --autodiscover true\n\nlint:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go fmt .\n\naudit:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go vet .\n\nunit_tests:\n  stage: check\n  extends:\n  - .run-on-push-and-in-mr\n  script:\n  - go install gotest.tools/gotestsum@latest\n  - gotestsum --junitfile report.xml\n  artifacts:\n    when: always\n    reports:\n      junit: report.xml\n\nbuild:\n  stage: build\n  extends:\n  - .run-on-push-and-in-mr\n  - .build-go\n\ntest:\n  stage: test\n  extends:\n  - .run-on-push-and-in-mr\n  - .test-go\n\ndeploy:\n  stage: deploy\n  rules:\n  - if: '$CI_COMMIT_REF_NAME == \"dev\" || $CI_COMMIT_REF_NAME == \"live\"'\n  environment:\n    name: ${CI_COMMIT_REF_NAME}\n  before_script:\n  - apt-get update\n  - apt-get -y install curl ca-certificates\n  script:\n  - |\n    curl https://seat${SEAT_INDEX}.${CI_COMMIT_REF_NAME}.webdav.inmylab.de/ \\\n        --fail \\\n        --verbose \\\n        --upload-file hello-linux-amd64 \\\n        --user seat${SEAT_INDEX}:${PASS}\n\npages:\n  stage: deploy\n  extends:\n  - .run-on-push-to-default-branch\n  image: registry.gitlab.com/gitlab-org/release-cli:v0.14.0\n  release:\n    tag_name: ${CI_PIPELINE_IID}\n    name: Release ${CI_PIPELINE_IID}\n    description: |\n      Some multi\n      line text\n    ref: ${CI_COMMIT_SHA}\n  script:\n  - cp hello-linux-amd64 public/hello\n  artifacts:\n    paths:\n    - public\n\npackage:\n  image: docker:20.10.18\n  stage: package\n  extends:\n  - .run-on-push-to-default-branch\n  services:\n  - name: docker:20.10.18-dind\n    command: [ \"dockerd\", \"--host\", \"tcp://0.0.0.0:2375\" ]\n  variables:\n    DOCKER_HOST: tcp://docker:2375\n  before_script:\n  - docker login -u \"${CI_REGISTRY_USER}\" -p \"${CI_REGISTRY_PASSWORD}\" \"${CI_REGISTRY}\"\n  script:\n  - docker build --tag \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\" .\n  - docker push \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}\"\n  after_script:\n  - docker logout \"${CI_REGISTRY}\"\n\ntrigger:\n  stage: trigger\n  extends:\n  - .run-on-push-to-default-branch\n  trigger:\n    include: child.yaml\n

If you want to jump to the solution, execute the following command:

git checkout upstream/160_gitlab_ci/280_security -- '*'\n

Heads-Up

You can also select a different scanner for container scanning using the variable $CS_ANALYZER_IMAGE. The following values are available:

Scanner Image Default (trivy) registry.gitlab.com/security-products/container-scanning:6 Grype registry.gitlab.com/security-products/container-scanning/grype:6 Trivy registry.gitlab.com/security-products/container-scanning/trivy:6"}]} \ No newline at end of file diff --git a/hands-on/2023-11-30/sitemap.xml.gz b/hands-on/2023-11-30/sitemap.xml.gz index 6eb71400..f55c378f 100644 Binary files a/hands-on/2023-11-30/sitemap.xml.gz and b/hands-on/2023-11-30/sitemap.xml.gz differ