doc/ci/components/examples.md
{{< details >}}
{{< /details >}}
Depending on a component's functionality, testing the component might require additional files in the repository. For example, a component which lints, builds, and tests software in a specific programming language requires actual source code samples. You can have source code examples, configuration files, and similar in the same repository.
For example, the Code Quality CI/CD component's has several code samples for testing.
Depending on a component's functionality, testing the component might require additional files in the repository.
The following "hello world" example for the Rust programming language uses the cargo tool chain for simplicity:
Go to the CI/CD component root directory.
Initialize a new Rust project by using the cargo init command.
cargo init
The command creates all required project files, including a src/main.rs "hello world" example.
This step is sufficient to build the Rust source code in a component job with cargo build.
tree
.
├── Cargo.toml
├── LICENSE.md
├── README.md
├── src
│ └── main.rs
└── templates
└── build.yml
Ensure that the component has a job to build the Rust source code, for example,
in templates/build.yml:
spec:
inputs:
stage:
default: build
description: 'Defines the build stage'
rust_version:
default: latest
description: 'Specify the Rust version, use values from https://hub.docker.com/_/rust/tags Defaults to latest'
---
"build-$[[ inputs.rust_version ]]":
stage: $[[ inputs.stage ]]
image: rust:$[[ inputs.rust_version ]]
script:
- cargo build --verbose
In this example:
stage and rust_version inputs can be modified from their default values.
The CI/CD job starts with a build- prefix and dynamically creates the name based on the rust_version input.
The command cargo build --verbose compiles the Rust source code.Test the component's build template in the project's .gitlab-ci.yml configuration file:
include:
# include the component located in the current project from the current SHA
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/build@$CI_COMMIT_SHA
inputs:
stage: build
stages: [build, test, release]
For running tests and more, add additional functions and tests into the Rust code,
and add a component template and job running cargo test in templates/test.yml.
spec:
inputs:
stage:
default: test
description: 'Defines the test stage'
rust_version:
default: latest
description: 'Specify the Rust version, use values from https://hub.docker.com/_/rust/tags Defaults to latest'
---
"test-$[[ inputs.rust_version ]]":
stage: $[[ inputs.stage ]]
image: rust:$[[ inputs.rust_version ]]
script:
- cargo test --verbose
Test the additional job in the pipeline by including the test component template:
include:
# include the component located in the current project from the current SHA
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/build@$CI_COMMIT_SHA
inputs:
stage: build
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/test@$CI_COMMIT_SHA
inputs:
stage: test
stages: [build, test, release]
This section provides practical examples of implementing common patterns in CI/CD components.
You can compose jobs with two conditionals by combining boolean type inputs and
extends functionality.
For example, to configure complex caching behavior with a boolean input:
spec:
inputs:
enable_special_caching:
description: 'If set to `true` configures a complex caching behavior'
type: boolean
---
.my-component:enable_special_caching:false:
extends: null
.my-component:enable_special_caching:true:
cache:
policy: pull-push
key: $CI_COMMIT_SHA
paths: [...]
my-job:
extends: '.my-component:enable_special_caching:$[[ inputs.enable_special_caching ]]'
script: ... # run some fancy tooling
This pattern works by passing the enable_special_caching input into
the extends keyword of the job.
Depending on whether enable_special_caching is true or false,
the appropriate configuration is selected from the predefined hidden jobs
(.my-component:enable_special_caching:true or .my-component:enable_special_caching:false).
options to conditionally configure jobsYou can compose jobs with multiple options, for behavior similar to if and elseif
conditionals. Use the extends with string type
and multiple options for any number of conditions.
For example, to configure complex caching behavior with 3 different options:
spec:
inputs:
cache_mode:
description: Defines the caching mode to use for this component
type: string
options:
- default
- aggressive
- relaxed
---
.my-component:cache_mode:default:
extends: null
.my-component:cache_mode:aggressive:
cache:
policy: push
key: $CI_COMMIT_SHA
paths: ['*/**']
.my-component:cache_mode:relaxed:
cache:
policy: pull-push
key: $CI_COMMIT_BRANCH
paths: ['bin/*']
my-job:
extends: '.my-component:cache_mode:$[[ inputs.cache_mode ]]'
script: ... # run some fancy tooling
In this example, cache_mode input offers default, aggressive, and relaxed options,
each corresponding to a different hidden job.
By extending the component job with extends: '.my-component:cache_mode:$[[ inputs.cache_mode ]]',
the job dynamically inherits the correct caching configuration based on the selected option.
{{< history >}}
ci_component_context_interpolation. Enabled by default.ci_component_context_interpolation removed.{{< /history >}}
Use component context CI/CD expressions to reference component metadata, like version and commit SHA. One use case is to build and publish versioned resources (like Docker images) with your component, and ensure the component uses the matching version.
For example, you can:
In the component project's release pipeline (.gitlab-ci.yml):
build-image:
stage: build
image: docker:latest
script:
- docker build -t $CI_REGISTRY_IMAGE/my-tool:$CI_COMMIT_TAG .
- docker push $CI_REGISTRY_IMAGE/my-tool:$CI_COMMIT_TAG
create-release:
stage: release
image: registry.gitlab.com/gitlab-org/cli:latest
script: echo "Creating release $CI_COMMIT_TAG"
rules:
- if: $CI_COMMIT_TAG
release:
tag_name: $CI_COMMIT_TAG
description: "Release $CI_COMMIT_TAG"
In the component template (templates/my-component/template.yml):
spec:
component: [version, reference]
inputs:
stage:
default: test
---
run-tool:
stage: $[[ inputs.stage ]]
image: $CI_REGISTRY_IMAGE/my-tool:$[[ component.version ]]
script:
- echo "Running tool version $[[ component.version ]]"
- echo "Component was included using reference: $[[ component.reference ]]"
- my-tool --version
In this example:
@1.0.0, the job uses the image my-tool:1.0.0.@1.0, it resolves to the latest 1.0.x version, for example 1.0.3,
and therefore uses my-tool:1.0.3.@~latest, it uses the latest released version.component.reference field shows the exact reference you specified, like 1.0, ~latest, or a SHA.
The reference could be useful for logging or debugging.This section shows practical examples of migrating CI/CD templates and pipeline configuration into reusable CI/CD components.
A complete pipeline for the software development lifecycle can be composed with multiple jobs and stages. CI/CD templates for programming languages may provide multiple jobs in a single template file. As a practice, the following Go CI/CD template should be migrated.
default:
image: golang:latest
stages:
- test
- build
- deploy
format:
stage: test
script:
- go fmt $(go list ./... | grep -v /vendor/)
- go vet $(go list ./... | grep -v /vendor/)
- go test -race $(go list ./... | grep -v /vendor/)
compile:
stage: build
script:
- mkdir -p mybinaries
- go build -o mybinaries ./...
artifacts:
paths:
- mybinaries
[!note] For a more incremental approach, migrate one job at a time. Start with the
buildjob, then repeat the steps for theformatandtestjobs.
The CI/CD template migration involves the following steps:
Analyze the CI/CD jobs and dependencies, and define migration actions:
image configuration is global, needs to be moved into the job definitions.format job runs multiple go commands in one job. The go test command should be moved
into a separate job to increase pipeline efficiency.compile job runs go build and should be renamed to build.Define optimization strategies for better pipeline efficiency.
stage job attribute should be configurable to allow different CI/CD pipeline consumers.image key uses a hardcoded image tag latest. Add golang_version as input
with latest as default value for more flexible and reusable pipelines. The input must match
the Docker Hub image tag values.compile job builds the binaries into a hard-coded target directory mybinaries,
which can be enhanced with a dynamic input and default value mybinaries.Create a template directory structure for the new component, based on one template for each job.
go command, for example format.yml, build.yml, and test.yml.README.md, LICENSE.md, .gitlab-ci.yml, .gitignore. The following shell commands
initialize the Go component structure:git init
mkdir templates
touch templates/{format,build,test}.yml
touch README.md LICENSE.md .gitlab-ci.yml .gitignore
git add -A
git commit -avm "Initial component structure"
git remote add origin https://gitlab.example.com/components/golang.git
git push
Create the CI/CD jobs as template. Start with the build job.
Define the following inputs in the spec section: stage, golang_version and binary_directory.
Add a dynamic job name definition, accessing inputs.golang_version.
Use the similar pattern for dynamic Go image versions, accessing inputs.golang_version.
Assign the stage to the inputs.stage value.
Create the binary director from inputs.binary_directory and add it as parameter to go build.
Define the artifacts path to inputs.binary_directory.
spec:
inputs:
stage:
default: 'build'
description: 'Defines the build stage'
golang_version:
default: 'latest'
description: 'Go image version tag'
binary_directory:
default: 'mybinaries'
description: 'Output directory for created binary artifacts'
---
"build-$[[ inputs.golang_version ]]":
image: golang:$[[ inputs.golang_version ]]
stage: $[[ inputs.stage ]]
script:
- mkdir -p $[[ inputs.binary_directory ]]
- go build -o $[[ inputs.binary_directory ]] ./...
artifacts:
paths:
- $[[ inputs.binary_directory ]]
The format job template follows the same patterns, but only requires the stage and golang_version inputs.
spec:
inputs:
stage:
default: 'format'
description: 'Defines the format stage'
golang_version:
default: 'latest'
description: 'Golang image version tag'
---
"format-$[[ inputs.golang_version ]]":
image: golang:$[[ inputs.golang_version ]]
stage: $[[ inputs.stage ]]
script:
- go fmt $(go list ./... | grep -v /vendor/)
- go vet $(go list ./... | grep -v /vendor/)
The test job template follows the same patterns, but only requires the stage and golang_version inputs.
spec:
inputs:
stage:
default: 'test'
description: 'Defines the format stage'
golang_version:
default: 'latest'
description: 'Golang image version tag'
---
"test-$[[ inputs.golang_version ]]":
image: golang:$[[ inputs.golang_version ]]
stage: $[[ inputs.stage ]]
script:
- go test -race $(go list ./... | grep -v /vendor/)
In order to test the component, modify the .gitlab-ci.yml configuration file,
and add tests.
Specify a different value for golang_version as input for the build job.
Modify the URL for your CI/CD component path.
stages: [format, build, test]
include:
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/format@$CI_COMMIT_SHA
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/build@$CI_COMMIT_SHA
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/build@$CI_COMMIT_SHA
inputs:
golang_version: "1.21"
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/test@$CI_COMMIT_SHA
inputs:
golang_version: latest
Add Go source code to test the CI/CD component. The go commands expect a Go project
with go.mod and main.go in the root directory.
Initialize the Go modules. Modify the URL for your CI/CD component path.
go mod init example.gitlab.com/components/golang
Create a main.go file with a main function, printing Hello, CI/CD component for example.
You can use code comments to generate Go code using GitLab Duo Code Suggestions.
// Specify the package, import required packages
// Create a main function
// Inside the main function, print "Hello, CI/CD Component"
package main
import "fmt"
func main() {
fmt.Println("Hello, CI/CD Component")
}
The directory tree should look as follows:
tree
.
├── LICENSE.md
├── README.md
├── go.mod
├── main.go
└── templates
├── build.yml
├── format.yml
└── test.yml
Follow the remaining steps in the converting a CI/CD template into a component section to complete the migration:
README.md and LICENSE.md files.The GitLab-maintained Go component provides an example for a successful migration from a Go CI/CD template, enhanced with inputs and component best practices. You can inspect the Git history to learn more.