CI/CD component examples
- Tier: Free, Premium, Ultimate
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
Test a component
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.
Example: Test a Rust language CI/CD component
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 initcommand.cargo initThe 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 withcargo build.tree . ├── Cargo.toml ├── LICENSE.md ├── README.md ├── src │ └── main.rs └── templates └── build.ymlEnsure 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 --verboseIn this example:
- The
stageandrust_versioninputs can be modified from their default values. The CI/CD job starts with abuild-prefix and dynamically creates the name based on therust_versioninput. The commandcargo build --verbosecompiles the Rust source code.
- The
Test the component’s
buildtemplate in the project’s.gitlab-ci.ymlconfiguration 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 testintemplates/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 --verboseTest the additional job in the pipeline by including the
testcomponent 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]
CI/CD component patterns
This section provides practical examples of implementing common patterns in CI/CD components.
Use boolean inputs to conditionally configure jobs
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 toolingThis 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).
Use options to conditionally configure jobs
You 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 toolingIn 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.
Use component context to reference versioned resources
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:
- Build a Docker image in the component’s release pipeline with a tag that matches the component version.
- Have the component reference that same image version.
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 --versionIn this example:
- If you include the component with
@1.0.0, the job uses the imagemy-tool:1.0.0. - If you include it with
@1.0, it resolves to the latest1.0.xversion, for example1.0.3, and therefore usesmy-tool:1.0.3. - If you include it with
@~latest, it uses the latest released version. - The
component.referencefield shows the exact reference you specified, like1.0,~latest, or a SHA. The reference could be useful for logging or debugging.
CI/CD component migration examples
This section shows practical examples of migrating CI/CD templates and pipeline configuration into reusable CI/CD components.
CI/CD component migration example: Go
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:
- mybinariesFor a more incremental approach, migrate one job at a time.
Start with the build job, then repeat the steps for the format and test jobs.
The CI/CD template migration involves the following steps:
Analyze the CI/CD jobs and dependencies, and define migration actions:
- The
imageconfiguration is global, needs to be moved into the job definitions. - The
formatjob runs multiplegocommands in one job. Thego testcommand should be moved into a separate job to increase pipeline efficiency. - The
compilejob runsgo buildand should be renamed tobuild.
- The
Define optimization strategies for better pipeline efficiency.
- The
stagejob attribute should be configurable to allow different CI/CD pipeline consumers. - The
imagekey uses a hardcoded image taglatest. Addgolang_versionas input withlatestas default value for more flexible and reusable pipelines. The input must match the Docker Hub image tag values. - The
compilejob builds the binaries into a hard-coded target directorymybinaries, which can be enhanced with a dynamic input and default valuemybinaries.
- The
Create a template directory structure for the new component, based on one template for each job.
- The name of the template should follow the
gocommand, for exampleformat.yml,build.yml, andtest.yml. - Create a new project, initialize a Git repository, add/commit all changes, set a remote origin and push. Modify the URL for your CI/CD component project path.
- Create additional files as outlined in the guidance to write a component:
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- The name of the template should follow the
Create the CI/CD jobs as template. Start with the
buildjob.Define the following inputs in the
specsection:stage,golang_versionandbinary_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.stagevalue.Create the binary director from
inputs.binary_directoryand add it as parameter togo 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
formatjob template follows the same patterns, but only requires thestageandgolang_versioninputs.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
testjob template follows the same patterns, but only requires thestageandgolang_versioninputs.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.ymlconfiguration file, and add tests.Specify a different value for
golang_versionas input for thebuildjob.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
gocommands expect a Go project withgo.modandmain.goin the root directory.Initialize the Go modules. Modify the URL for your CI/CD component path.
go mod init example.gitlab.com/components/golangCreate a
main.gofile with a main function, printingHello, CI/CD componentfor 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:
- Commit and push the changes, and verify the CI/CD pipeline results.
- Follow the guidance on writing a component to update the
README.mdandLICENSE.mdfiles. - Release the component and verify it in the CI/CD catalog.
- Add the CI/CD component into your staging/production environment.
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.