summaryrefslogtreecommitdiff
path: root/doc/ci/quick_start/tutorial.md
blob: 3e12c3190696e8f8ac736682fcd936e8c7333e1d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
---
stage: Verify
group: Pipeline Authoring
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---

# Tutorial: Create a complex pipeline

This tutorial walks you through configuring a progressively more complex CI/CD pipeline
through small, iterative steps. The pipeline is always fully functional,
but it gains more functionality with each step.

When you finish this tutorial, you will have a new project on GitLab.com and a working documentation site on
[Docusaurus](https://docusaurus.io/).

To complete this tutorial, you will:

1. Create a project to hold the Docusaurus files
1. Create the initial pipeline configuration file
1. Add a job to build the site
1. Add a job to deploy the site
1. Add test jobs
1. Start using merge request pipelines
1. Reduce duplicated configuration

## Prerequisites

- You need an account on GitLab.com.
- You should be familiar with Git.
- Node.js must be installed on your local machine. For example, on macOS you can
  [install node](https://formulae.brew.sh/formula/node) with `brew install node`.

## Create a project to hold the Docusaurus files

Before adding the pipeline configuration, you must first set up a Docusaurus project
on GitLab.com:

1. Create a new project under your username (not a group):
   1. On the top bar, select **Main menu > Projects > View all projects**.
   1. On the right of the page, select **New project**.
   1. Select **Create blank project**.
   1. Enter the project details:
      - In the **Project name** field, enter the name of your project, for example `My Pipeline Tutorial Project`.
      - Select **Initialize repository with a README**.
   1. Select **Create project**.
1. On the right of the **Project Overview** page for your project, select **Clone**
   to find the clone paths for your project. Copy the SSH or HTTP path and use the path
   to clone the project locally.

   For example, to clone with SSH into a `pipeline-tutorial` directory on your computer:

   ```shell
   git clone git@gitlab.com:my-username/my-pipeline-tutorial-project.git pipeline-tutorial
   ```

1. Change to the project's directory, then generate a new Docusaurus site:

   ```shell
   cd pipeline-tutorial
   npm init docusaurus
   ```

   The Docusaurus initialization wizard prompts you with questions about the site.
   Use all the default options.

1. The initialization wizard sets up the site in `website/`, but the site should be in
   the root of the project. Move the files up to the root and delete the old directory:

   ```shell
   mv website/* .
   rm -r website
   ```

1. Update the Docusaurus configuration file with the details of your GitLab project.
   In `docusaurus.config.js`:

   - Set `url:` to a path with this format: `https://<my-username>.gitlab.io/`.
   - Set `baseUrl:` to your project name, like `/my-pipeline-tutorial-project/`.

1. Commit the changes, and push them to GitLab:

   ```shell
   git add .
   git commit -m "Add simple generated Docusaurus site"
   git push origin
   ```

## Create the initial CI/CD configuration file

Start with the simplest possible pipeline configuration file to ensure CI/CD is enabled
in the project and runners are available to run jobs.

This step introduces:

- [Jobs](../jobs/index.md): These are self-contained parts of a pipeline that run your commands.
  Jobs run on [runners](../runners/index.md), separate from the GitLab instance.
- [`script`](../yaml/index.md#script): This section of a job's configuration is
  where you define the commands for jobs. If there are multiple commands (in an array),
  they run in order. Each command executes as if it was run as a CLI command.
  By default, if a command fails or returns an error, the job is flagged as failed
  and no more commands run.

In this step, create a `.gitlab-ci.yml` file in the root of the project with this configuration:

```yaml
test-job:
  script:
    - echo "This is my first job!"
    - date
```

Commit and push this change to GitLab, then:

1. Go to **Build > Pipelines** and make sure a pipeline runs in GitLab with this single job.
1. Select the pipeline, then select the job to view the job's log and see the `This is my first job!` message
   followed by the date.

Now that you have a `.gitlab-ci.yml` file in your project, you can make all future changes
to pipeline configuration with the [pipeline editor](../pipeline_editor/index.md).

## Add a job to build the site

A common task for a CI/CD pipeline is to build the code in the project then deploy it.
Start by adding a job that builds the site.

This step introduces:

- [`image`](../yaml/index.md#image): Tell the runner which Docker
  container to use to run the job in. The runner:
  1. Downloads the container image and starts it.
  1. Clones your GitLab project into the running container.
  1. Runs the `script` commands, one at a time.
- [`artifacts`](../yaml/index.md#artifacts): Jobs are self-contained and do not share
  resources with each other. If you want files generated in one job to be used in
  another job, you must save them as artifacts first. Then later jobs can retrieve the
  artifacts and use the generated files.

In this step, replace `test-job` with `build-job`:

- Use `image` to configure the job to run with the latest `node` image. Docusaurus
  is a Node.js project and the `node` image has the needed `npm` commands built in.
- Run `npm install` to install Docusaurus into the running `node` container, then run
  `npm run build` to build the site.
- Docusaurus saves the built site in `build/`, so save these files with `artifacts`.

```yaml
build-job:
  image: node
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - "build/"
```

Use the pipeline editor to commit this pipeline configuration to the default branch,
and check the job log. You can:

- See the `npm` commands run and build the site.
- Verify that the artifacts are saved at the end.
- Browse the contents of the artifacts file by selecting **Browse** to the right of the job log
  after the job completes.

## Add a job to deploy the site

After verifying the Docusaurus site builds in `build-job`, you can add a job that deploys it.

This step introduces:

- [`stage`](../yaml/index.md#stage) and [`stages](../yaml/index.md#stage): The most common
  pipeline configurations group jobs into stages. Jobs in the same stage can run in parallel,
  while jobs in later stages wait for jobs in earlier stages to complete. If a job fails,
  the whole stage is considered failed and jobs in later stages do not start running.
- [GitLab Pages](../../user/project/pages/index.md): To host your static site, you
  will use GitLab Pages.

In this step:

- Add a job that fetches the built site and deploys it. When using GitLab Pages,
  the job is always named `pages`. The artifacts from the `build-job` are fetched automatically
  and extracted into the job. Pages looks for the site in the `public/` directory though,
  so add a `script` command to move the site to that directory.
- Add a `stages` section, and define the stages for each job. `build-job` runs first
  in the `build` stage, and `pages` runs after in the `deploy` stage.

```yaml
stages:          # List of stages for jobs and their order of execution
  - build
  - deploy

build-job:
  stage: build   # Set this job to run in the `build` stage
  image: node
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - "build/"

pages:
  stage: deploy  # Set this new job to run in the `deploy` stage
  script:
    - mv build/ public/
  artifacts:
    paths:
      - "public/"
```

Use the pipeline editor to commit this pipeline configuration to the default branch,
and view the pipeline details from the **Pipelines** list. Verify that:

- The two jobs run in different stages, `build` and `deploy`.
- After the `pages` job completes a `pages-deploy` job appears, which is the GitLab process
  that deploys the Pages site. When that job completes, you can visit your new Docusaurus
  site. The Pages documentation explains [the URL formatting](../../user/project/pages/getting_started_part_one.md#gitlab-pages-default-domain-names),
  which should be similar to `https://<my-username>.gitlab.io/<my-pipeline-tutorial-project>/`.

## Add test jobs

Now that the site builds and deploys as expected, you can add tests and linting.
For example, a Ruby project might run RSpec test jobs. Docusaurus is a static site
that uses Markdown and generated HTML, so this tutorial adds jobs to test the Markdown and HTML.

This step introduces:

- [`allow_failure`](../yaml/index.md#allow_failure): Jobs that fail intermittently,
  or are expected to fail, can slow down productivity or be difficult to troubleshoot.
  Use `allow_failure` to let jobs fail without halting pipeline execution.
- [`dependencies`](../yaml/index.md#dependencies): Use `dependencies` to control
  artifact downloads in individual jobs by listing which jobs to fetch artifacts from.

In this step:

- Add a new `test` stage that runs between `build` and `deploy`. These three stages
  are the default stages when `stages` is undefined in the configuration.
- Add a `lint-markdown` job to run [markdownlint](https://github.com/DavidAnson/markdownlint)
  and check the Markdown in your project. markdownlint is a static analysis tool that
  checks that your Markdown files follow formatting standards.
  - The sample Markdown files Docusaurus generates are in `blog/` and `docs/`.
  - This tool scans the original Markdown files only, and does not need the generated HTML
    saved in the `build-job` artifacts. Speed up the job with `dependencies: []`
    so that it fetches no artifacts.
  - A few of the sample Markdown files violate default markdownlint rules, so add
    `allow_failure: true` to let the pipeline continue despite the rule violations.
- Add a `test-html` job to run [HTMLHint](https://htmlhint.com/) and check the generated HTML.
  HTMLHint is a static analysis tool that scans generated HTML for known issues.
- Both `test-html` and `pages` need the generated HTML found in the `build-job` artifacts.
  Jobs fetch artifacts from all jobs in earlier stages by default, but add `dependencies:`
  to make sure the jobs don't accidentally download other artifacts after future pipeline changes.

```yaml
stages:
  - build
  - test               # Add a `test` stage for the test jobs
  - deploy

build-job:
  stage: build
  image: node
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - "build/"

lint-markdown:
  stage: test
  image: node
  dependencies: []     # Don't fetch any artifacts
  script:
    - npm install markdownlint-cli2 --global           # Install markdownlint into the container
    - markdownlint-cli2 -v                             # Verify the version, useful for troubleshooting
    - markdownlint-cli2 "blog/**/*.md" "docs/**/*.md"  # Lint all markdown files in blog/ and docs/
  allow_failure: true  # This job fails right now, but don't let it stop the pipeline.

test-html:
  stage: test
  image: node
  dependencies:
    - build-job        # Only fetch artifacts from `build-job`
  script:
    - npm install --save-dev htmlhint                  # Install HTMLHint into the container
    - npx htmlhint --version                           # Verify the version, useful for troubleshooting
    - npx htmlhint build/                              # Lint all markdown files in blog/ and docs/

pages:
  stage: deploy
  dependencies:
    - build-job        # Only fetch artifacts from `build-job`
  script:
    - mv build/ public/
  artifacts:
    paths:
      - "public/"
```

Commit this pipeline configuration to the default branch, and view the pipeline details.

- The `test-markdown` job fails because the sample Markdown violates the default
  markdownlint rules, but is allowed to fail. You can:
  - Ignore the violations for now. They do not need to be fixed as part of the tutorial.
  - Fix the Markdown file violations. Then you can change `allow_failure` to `false`,
    or remove `allow_failure` completely because `allow_failure: false` is the default behavior
    when not defined.
  - Add a markdownlint configuration file to limit which rule violations to alert on.
- You can also make changes to the Markdown file content and see the changes on the site
  after the next deployment.

## Start using merge request pipelines

With the pipeline configurations above, the site deploys every time a pipeline completes
successfully, but this is not an ideal development workflow. It's better to work from
feature branches and merge requests, and only deploy the site when changes merge
to the default branch.

This step introduces:

- [`rules`](../yaml/index.md#rules): Add rules to each job to configure in which
  pipelines they run. You can configure jobs to run in [merge request pipelines](../pipelines/merge_request_pipelines.md),
  [scheduled pipelines](../pipelines/schedules.md), or other specific situations.
  Rules are evaluated from top to bottom, and if a rule matches, the job is
  added to the pipeline.
- [CI/CD variables](../variables/index.md): use these environment variables
  to configure job behavior in the configuration file and in script commands.
  [Predefined CI/CD variables](../variables/predefined_variables.md) are variables
  that you do not need to manually define. They are automatically injected into pipelines
  so you can use them to configure your pipeline. Variables are usually formatted as `$VARIABLE_NAME`.
  and predefined variables are usually prefixed with `$CI_`.

In this step:

- Create a new feature branch and make the changes in the branch instead of the default branch.
- Add `rules` to each job:
  - The site should only deploy for changes to the default branch.
  - The other jobs should run for all changes in merge requests or the default branch.
- With this pipeline configuration, you can work from a feature branch without running any jobs,
  which saves resources. When you are ready to validate your changes, create a merge request
  and a pipeline runs with the jobs configured to run in merge requests.
- When your merge request is accepted and the changes merge to the default branch,
  a new pipeline runs which also contains the `pages` deployment job. The site deploys
  if no jobs fail.

```yaml
stages:
  - build
  - test
  - deploy

build-job:
  stage: build
  image: node
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - "build/"
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'  # Run for all changes to a merge request's source branch
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH       # Run for all changes to the default branch

lint-markdown:
  stage: test
  image: node
  dependencies: []
  script:
    - npm install markdownlint-cli2 --global
    - markdownlint-cli2 -v
    - markdownlint-cli2 "blog/**/*.md" "docs/**/*.md"
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'  # Run for all changes to a merge request's source branch
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH       # Run for all changes to the default branch

test-html:
  stage: test
  image: node
  dependencies:
    - build-job
  script:
    - npm install --save-dev htmlhint
    - npx htmlhint --version
    - npx htmlhint build/
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'  # Run for all changes to a merge request's source branch
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH       # Run for all changes to the default branch

pages:
  stage: deploy
  dependencies:
    - build-job
  script:
    - mv build/ public/
  artifacts:
    paths:
      - "public/"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH      # Run for all changes to the default branch only
```

Merge the changes in your merge request. This action updates the default branch. Verify that
the new pipeline contains the `pages` job that deploys the site.

Be sure to use feature branches and merge requests for all future changes to pipeline configuration.
Other project changes, like creating a Git tag or adding a pipeline schedule, do not
trigger pipelines unless you add rules for those cases too.

## Reduce duplicated configuration

The pipeline now contains three jobs that all have identical `rules` and `image`
configuration. Instead of repeating these rules, use `extends` and `default` to create
single sources of truth.

This step introduces:

- [Hidden jobs](../jobs/index.md#hide-jobs): Jobs that start with `.` are never
  added to a pipeline. Use them to hold configuration you want to reuse.
- [`extends`](../yaml/index.md#extends): Use extends to repeat configuration in
  multiple places, often from hidden jobs. If you update the hidden job's configuration,
  all jobs extending the hidden job use the updated configuration.
- [`default`](../yaml/index.md#default): Set keyword defaults that apply to all jobs
  when not defined.
- YAML overriding: When reusing configuration with `extends` or `default`, you can explicitly
  define a keyword in the job to override the `extends` or `default` configuration.

In this step:

- Add a `.standard-rules` hidden job to hold the rules that are repeated in `build-job`,
  `lint-markdown`, and `test-html`.
- Use `extends` to reuse the `.standard-rules` configuration in the three jobs.
- Add a `default` section to define the `image` default as `node`.
- The `pages` deployment job does not need the default `node` image, so explicitly use
  [`busybox`](https://hub.docker.com/_/busybox), an extremely tiny and fast image.

```yaml
stages:
  - build
  - test
  - deploy

default:               # Add a default section to define the `image` keyword's default value
  image: node

.standard-rules:       # Make a hidden job to hold the common rules
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

build-job:
  extends:
    - .standard-rules  # Reuse the configuration in `.standard-rules` here
  stage: build
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - "build/"

lint-markdown:
  stage: test
  extends:
    - .standard-rules  # Reuse the configuration in `.standard-rules` here
  dependencies: []
  script:
    - npm install markdownlint-cli2 --global
    - markdownlint-cli2 -v
    - markdownlint-cli2 "blog/**/*.md" "docs/**/*.md"
  allow_failure: true

test-html:
  stage: test
  extends:
    - .standard-rules  # Reuse the configuration in `.standard-rules` here
  dependencies:
    - build-job
  script:
    - npm install --save-dev htmlhint
    - npx htmlhint --version
    - npx htmlhint build/

pages:
  stage: deploy
  image: busybox       # Override the default `image` value with `busybox`
  dependencies:
    - build-job
  script:
    - mv build/ public/
  artifacts:
    paths:
      - "public/"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
```

Use a merge request to commit this pipeline configuration to the default branch.
The file is simpler, but it should have the same behavior as the previous step.

You've just created a full pipeline and streamlined it to be more efficient. Nice work!
Now you can take this knowledge, learn about [the rest of the `.gitlab-ci.yml` keywords](../yaml/index.md),
and build your own pipelines.