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.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 --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 --verbose -
Test 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 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).
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 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.
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:
- mybinaries
For 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/golang -
Create 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.