TUN-9800: Migrate cloudflared-ci pipelines to Gitlab CI

## Summary

This commit migrates the cloduflared ci pipelines, that built, tested and component tested the linux binaries to gitlab ci.

The only thing that is remaining to move from teamcity to gitlab are now the release pipelines that run on master.

Relates to TUN-9800
This commit is contained in:
João "Pisco" Fernandes 2025-09-11 11:33:24 +01:00
parent d9e13ab2ab
commit 173396be90
14 changed files with 209 additions and 83 deletions

View File

@ -8,10 +8,9 @@ include:
inputs: inputs:
stage: pre-build stage: pre-build
jobPrefix: ci-image jobPrefix: ci-image
# runOnChangesTo: [".ci/image/**"] runOnChangesTo: [".ci/image/**"]
# runOnMR: true runOnMR: true
# runOnBranches: '^master$' runOnBranches: '^master$'
runOnBranches: "^.+$"
commentImageRefs: false commentImageRefs: false
runner: vm-linux-x86-4cpu-8gb runner: vm-linux-x86-4cpu-8gb
EXTRA_DIB_ARGS: "--manifest=.ci/image/.docker-images" EXTRA_DIB_ARGS: "--manifest=.ci/image/.docker-images"
@ -23,9 +22,8 @@ include:
inputs: inputs:
stage: pre-build stage: pre-build
jobPrefix: ci-image jobPrefix: ci-image
# runOnMR: true runOnMR: true
# runOnBranches: '^master$' runOnBranches: '^master$'
runOnBranches: "^.+$"
IMAGE_PATH: "$REGISTRY_HOST/stash/tun/cloudflared/ci-image/master" IMAGE_PATH: "$REGISTRY_HOST/stash/tun/cloudflared/ci-image/master"
VARIABLE_NAME: BUILD_IMAGE VARIABLE_NAME: BUILD_IMAGE
needs: needs:

View File

@ -5,13 +5,19 @@
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: always when: always
- when: never - when: never
# Rules to run the job only on branches that are not master. This is needed because for now # Rules to run the job only on merge requests
# we need to keep a similar behavior due to the integration with teamcity, which requires us run-on-mr:
# to not trigger pipelines on tags and/or merge requests.
run-on-branch:
- if: $CI_COMMIT_TAG - if: $CI_COMMIT_TAG
when: never when: never
- if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
- when: never
# Rules to run the job on merge_requests and master branch
run-always:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH != null && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: always when: always
- when: never - when: never
@ -28,4 +34,20 @@
else else
echo "No tag present — skipping." echo "No tag present — skipping."
exit 0 exit 0
fi fi
.component-tests:
image: $BUILD_IMAGE
rules:
- !reference [.default-rules, run-always]
variables:
COMPONENT_TESTS_CONFIG: component-test-config.yaml
COMPONENT_TESTS_CONFIG_CONTENT: Y2xvdWRmbGFyZWRfYmluYXJ5OiBjbG91ZGZsYXJlZC5leGUKY3JlZGVudGlhbHNfZmlsZTogY3JlZC5qc29uCm9yaWdpbmNlcnQ6IGNlcnQucGVtCnpvbmVfZG9tYWluOiBhcmdvdHVubmVsdGVzdC5jb20Kem9uZV90YWc6IDQ4Nzk2ZjFlNzBiYjc2NjljMjliYjUxYmEyODJiZjY1
secrets:
DNS_API_TOKEN:
vault: gitlab/cloudflare/tun/cloudflared/_dev/_terraform_atlantis/component_tests_token/data@kv
file: false
COMPONENT_TESTS_ORIGINCERT:
vault: gitlab/cloudflare/tun/cloudflared/_dev/component_tests_cert_pem/data@kv
file: false
cache: {}

View File

@ -7,8 +7,9 @@ RUN apt-get update && \
apt-get install --no-install-recommends --allow-downgrades -y \ apt-get install --no-install-recommends --allow-downgrades -y \
build-essential \ build-essential \
git \ git \
go-boring=1.24.4-1 \ go-boring=1.24.6-1 \
libffi-dev \ libffi-dev \
procps \
python3-dev \ python3-dev \
python3-pip \ python3-pip \
python3-setuptools \ python3-setuptools \

90
.ci/linux.gitlab-ci.yml Normal file
View File

@ -0,0 +1,90 @@
.golang-inputs: &golang_inputs
runOnMR: true
runOnBranches: '^master$'
outputDir: artifacts
runner: linux-x86-8cpu-16gb
stage: build
golangVersion: "boring-1.24"
CGO_ENABLED: 1
include:
###################
### Linux Build ###
###################
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
inputs:
<<: *golang_inputs
jobPrefix: linux-build
GOLANG_MAKE_TARGET: ci-build
########################
### Linux FIPS Build ###
########################
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
inputs:
<<: *golang_inputs
jobPrefix: linux-fips-build
GOLANG_MAKE_TARGET: ci-fips-build
#################
### Unit Tests ##
#################
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
inputs:
<<: *golang_inputs
stage: test
jobPrefix: test
GOLANG_MAKE_TARGET: ci-test
######################
### Unit Tests FIPS ##
######################
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
inputs:
<<: *golang_inputs
stage: test
jobPrefix: test-fips
GOLANG_MAKE_TARGET: ci-fips-test
#################
### Vuln Check ##
#################
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
inputs:
<<: *golang_inputs
runOnBranches: '^$'
stage: validate
jobPrefix: vulncheck
GOLANG_MAKE_TARGET: vulncheck
#################################
### Run Linux Component Tests ###
#################################
component-tests-linux: &component-tests-linux
stage: test
extends: .component-tests
needs:
- ci-image-get-image-ref
- linux-build-boring-make
script:
- ./.ci/scripts/component-tests.sh
variables: &component-tests-variables
CI: 1
COMPONENT_TESTS_CONFIG_CONTENT: Y2xvdWRmbGFyZWRfYmluYXJ5OiBjbG91ZGZsYXJlZApjcmVkZW50aWFsc19maWxlOiBjcmVkLmpzb24Kb3JpZ2luY2VydDogY2VydC5wZW0Kem9uZV9kb21haW46IGFyZ290dW5uZWx0ZXN0LmNvbQp6b25lX3RhZzogNDg3OTZmMWU3MGJiNzY2OWMyOWJiNTFiYTI4MmJmNjU=
tags:
- linux-x86-8cpu-16gb
artifacts:
reports:
junit: report.xml
######################################
### Run Linux FIPS Component Tests ###
######################################
component-tests-linux-fips:
<<: *component-tests-linux
needs:
- ci-image-get-image-ref
- linux-fips-build-boring-make
variables:
<<: *component-tests-variables
COMPONENT_TESTS_FIPS: 1

View File

@ -6,7 +6,7 @@ include:
############################### ###############################
.mac-build-defaults: &mac-build-defaults .mac-build-defaults: &mac-build-defaults
rules: rules:
- !reference [.default-rules, run-on-branch] - !reference [.default-rules, run-on-mr]
tags: tags:
- "macstadium-${RUNNER_ARCH}" - "macstadium-${RUNNER_ARCH}"
parallel: parallel:

25
.ci/scripts/component-tests.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
set -e -o pipefail
# Fetch cloudflared from the artifacts folder
mv ./artifacts/cloudflared ./cloudflared
python3 -m venv env
. env/bin/activate
pip install --upgrade -r component-tests/requirements.txt
# Creates and routes a Named Tunnel for this build. Also constructs
# config file from env vars.
python3 component-tests/setup.py --type create
# Define the cleanup function
cleanup() {
# The Named Tunnel is deleted and its route unprovisioned here.
python3 component-tests/setup.py --type cleanup
}
# The trap will call the cleanup function on script exit
trap cleanup EXIT
pytest component-tests -o log_cli=true --log-cli-level=INFO --junit-xml=report.xml

View File

@ -1,8 +1,7 @@
#!/bin/bash #!/bin/bash
set -e -o pipefail set -e -o pipefail
OUTPUT=$(goimports -l -d -local github.com/cloudflare/cloudflared $(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc)) OUTPUT=$(go run -mod=readonly golang.org/x/tools/cmd/goimports@v0.30.0 -l -d -local github.com/cloudflare/cloudflared $(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc))
if [ -n "$OUTPUT" ] ; then if [ -n "$OUTPUT" ] ; then
PAGER=$(which colordiff || echo cat) PAGER=$(which colordiff || echo cat)

View File

@ -31,7 +31,7 @@ Write-Host "Running component tests"
try { try {
python -m pip --disable-pip-version-check install --upgrade -r component-tests/requirements.txt --use-pep517 python -m pip --disable-pip-version-check install --upgrade -r component-tests/requirements.txt --use-pep517
python component-tests/setup.py --type create python component-tests/setup.py --type create
python -m pytest component-tests -o log_cli=true --log-cli-level=INFO python -m pytest component-tests -o log_cli=true --log-cli-level=INFO --junit-xml=report.xml
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
throw "Failed component tests" throw "Failed component tests"
} }

View File

@ -3,7 +3,7 @@ Param(
[string]$ScriptToExecute [string]$ScriptToExecute
) )
# This script its a wrapper that downloads a specific version # The script is a wrapper that downloads a specific version
# of go, adds it to the PATH and executes a script with that go # of go, adds it to the PATH and executes a script with that go
# version in the path. # version in the path.

View File

@ -6,7 +6,7 @@ include:
################################### ###################################
.windows-build-defaults: &windows-build-defaults .windows-build-defaults: &windows-build-defaults
rules: rules:
- !reference [.default-rules, run-on-branch] - !reference [.default-rules, run-always]
tags: tags:
- windows-x86 - windows-x86
cache: {} cache: {}
@ -27,27 +27,20 @@ build-cloudflared-windows:
### Load Environment Variables for Component Tests ### ### Load Environment Variables for Component Tests ###
###################################################### ######################################################
load-windows-env-variables: load-windows-env-variables:
rules:
- !reference [.default-rules, run-on-branch]
stage: pre-build stage: pre-build
extends: .component-tests
script: script:
- echo "COMPONENT_TESTS_CONFIG=component-test-config.yaml" >> windows.env - echo "COMPONENT_TESTS_CONFIG=$COMPONENT_TESTS_CONFIG" >> windows.env
- echo "COMPONENT_TESTS_CONFIG_CONTENT=Y2xvdWRmbGFyZWRfYmluYXJ5OiBjbG91ZGZsYXJlZC5leGUKY3JlZGVudGlhbHNfZmlsZTogY3JlZC5qc29uCm9yaWdpbmNlcnQ6IGNlcnQucGVtCnpvbmVfZG9tYWluOiBhcmdvdHVubmVsdGVzdC5jb20Kem9uZV90YWc6IDQ4Nzk2ZjFlNzBiYjc2NjljMjliYjUxYmEyODJiZjY1" >> windows.env - echo "COMPONENT_TESTS_CONFIG_CONTENT=$COMPONENT_TESTS_CONFIG_CONTENT" >> windows.env
- echo "DNS_API_TOKEN=$DNS_API_TOKEN" >> windows.env - echo "DNS_API_TOKEN=$DNS_API_TOKEN" >> windows.env
# We have to encode the `COMPONENT_TESTS_ORIGINCERT` secret, because it content is a file, otherwise we can't export it using gitlab # We have to encode the `COMPONENT_TESTS_ORIGINCERT` secret, because it content is a file, otherwise we can't export it using gitlab
- echo "COMPONENT_TESTS_ORIGINCERT=$(echo "$COMPONENT_TESTS_ORIGINCERT" | base64 -w0)" >> windows.env - echo "COMPONENT_TESTS_ORIGINCERT=$(echo "$COMPONENT_TESTS_ORIGINCERT" | base64 -w0)" >> windows.env
secrets: variables:
DNS_API_TOKEN: COMPONENT_TESTS_CONFIG_CONTENT: Y2xvdWRmbGFyZWRfYmluYXJ5OiBjbG91ZGZsYXJlZC5leGUKY3JlZGVudGlhbHNfZmlsZTogY3JlZC5qc29uCm9yaWdpbmNlcnQ6IGNlcnQucGVtCnpvbmVfZG9tYWluOiBhcmdvdHVubmVsdGVzdC5jb20Kem9uZV90YWc6IDQ4Nzk2ZjFlNzBiYjc2NjljMjliYjUxYmEyODJiZjY1
vault: gitlab/cloudflare/tun/cloudflared/_dev/_terraform_atlantis/component_tests_token/data@kv
file: false
COMPONENT_TESTS_ORIGINCERT:
vault: gitlab/cloudflare/tun/cloudflared/_dev/component_tests_cert_pem/data@kv
file: false
artifacts: artifacts:
access: 'none' access: 'none'
reports: reports:
dotenv: windows.env dotenv: windows.env
cache: {}
################################### ###################################
### Run Windows Component Tests ### ### Run Windows Component Tests ###
@ -60,6 +53,9 @@ component-tests-cloudflared-windows:
# We have to decode the secret we encoded on the `load-windows-env-variables` job # We have to decode the secret we encoded on the `load-windows-env-variables` job
- $env:COMPONENT_TESTS_ORIGINCERT = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($env:COMPONENT_TESTS_ORIGINCERT)) - $env:COMPONENT_TESTS_ORIGINCERT = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($env:COMPONENT_TESTS_ORIGINCERT))
- powershell -ExecutionPolicy Bypass -File ".\.ci\scripts\windows\go-wrapper.ps1" "${GO_VERSION}" ".\.ci\scripts\windows\component-test.ps1" - powershell -ExecutionPolicy Bypass -File ".\.ci\scripts\windows\go-wrapper.ps1" "${GO_VERSION}" ".\.ci\scripts\windows\component-test.ps1"
artifacts:
reports:
junit: report.xml
################################ ################################
### Package Windows Binaries ### ### Package Windows Binaries ###

View File

@ -1,27 +1,13 @@
variables: variables:
# Define GOPATH within the project directory to allow GitLab CI to cache it. GO_VERSION: "go1.24.6"
# By default, Go places modules in GOMODCACHE, often outside the project. GIT_DEPTH: "0"
# Explicitly setting GOMODCACHE ensures it's within the cached path.
GOPATH: "$CI_PROJECT_DIR/.go"
GOMODCACHE: "$GOPATH/pkg/mod"
GO_BIN_DIR: "$GOPATH/bin"
GO_VERSION: "go1.24.4"
cache:
# Cache Go modules and the binaries.
# The 'key' ensures a unique cache per branch, or you can use a fixed key
# for a shared cache across all branches if that fits your workflow.
key: "$CI_COMMIT_REF_SLUG"
paths:
- ${GOPATH}/pkg/mod/ # For Go modules
- ${GO_BIN_DIR}/
default: default:
id_tokens: id_tokens:
VAULT_ID_TOKEN: VAULT_ID_TOKEN:
aud: https://vault.cfdata.org aud: https://vault.cfdata.org
stages: [pre-build, build, test, package, release] stages: [pre-build, build, validate, test, package, release]
include: include:
##################################################### #####################################################
@ -34,6 +20,11 @@ include:
##################################################### #####################################################
- local: .ci/ci-image.gitlab-ci.yml - local: .ci/ci-image.gitlab-ci.yml
#####################################################
################## Linux Builds ###################
#####################################################
- local: .ci/linux.gitlab-ci.yml
##################################################### #####################################################
################## Windows Builds ################### ################## Windows Builds ###################
##################################################### #####################################################
@ -48,27 +39,3 @@ include:
################# Release Packages ################## ################# Release Packages ##################
##################################################### #####################################################
- local: .ci/release.gitlab-ci.yml - local: .ci/release.gitlab-ci.yml
# Template for Go setup, including caching and installation
.go_setup:
image: docker-registry.cfdata.org/stash/devtools/ci-builders/golang-1.24/master:3219-cc8b513@sha256:4fe3ff47ba07f9b23429f49fbd063cc1a34156dd11b8113e325ad3762f94a1db
before_script:
- mkdir -p ${GOPATH} ${GOMODCACHE} ${GO_BIN_DIR}
- export PATH=$PATH:${GO_BIN_DIR}
- go env -w GOMODCACHE=${GOMODCACHE} # Ensure go uses the cached module path
# Check if govulncheck is already installed and install it if not
- if [ ! -f ${GO_BIN_DIR}/govulncheck ]; then
echo "govulncheck not found in cache, installing...";
go install golang.org/x/vuln/cmd/govulncheck@latest;
else
echo "govulncheck found in cache, skipping installation.";
fi
vulncheck:
stage: build
extends: .go_setup
rules:
- !reference [.default-rules, run-on-branch]
script:
- make vulncheck

View File

@ -128,6 +128,8 @@ endif
#for FIPS compliance, FPM defaults to MD5. #for FIPS compliance, FPM defaults to MD5.
RPM_DIGEST := --rpm-digest sha256 RPM_DIGEST := --rpm-digest sha256
GO_TEST_LOG_OUTPUT = /tmp/gotest.log
.PHONY: all .PHONY: all
all: cloudflared test all: cloudflared test
@ -137,7 +139,7 @@ clean:
.PHONY: vulncheck .PHONY: vulncheck
vulncheck: vulncheck:
@govulncheck ./... @go run -mod=readonly golang.org/x/vuln/cmd/govulncheck@latest ./...
.PHONY: cloudflared .PHONY: cloudflared
cloudflared: cloudflared:
@ -160,11 +162,9 @@ generate-docker-version:
.PHONY: test .PHONY: test
test: vet test: vet
ifndef CI $Q go test -json -v -mod=vendor -race $(LDFLAGS) ./... 2>&1 | tee $(GO_TEST_LOG_OUTPUT)
go test -v -mod=vendor -race $(LDFLAGS) ./... ifneq ($(FIPS), true)
else @go run -mod=readonly github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest -input $(GO_TEST_LOG_OUTPUT)
@mkdir -p .cover
go test -v -mod=vendor -race $(LDFLAGS) -coverprofile=".cover/c.out" ./...
endif endif
.PHONY: cover .PHONY: cover
@ -254,7 +254,7 @@ capnp:
.PHONY: vet .PHONY: vet
vet: vet:
go vet -mod=vendor github.com/cloudflare/cloudflared/... $Q go vet -mod=vendor github.com/cloudflare/cloudflared/...
.PHONY: fmt .PHONY: fmt
fmt: fmt:
@ -263,7 +263,7 @@ fmt:
.PHONY: fmt-check .PHONY: fmt-check
fmt-check: fmt-check:
@./fmt-check.sh @./.ci/scripts/fmt-check.sh
.PHONY: lint .PHONY: lint
lint: lint:
@ -272,3 +272,23 @@ lint:
.PHONY: mocks .PHONY: mocks
mocks: mocks:
go generate mocks/mockgen.go go generate mocks/mockgen.go
.PHONY: ci-build
ci-build:
@GOOS=linux GOARCH=amd64 $(MAKE) cloudflared
@mkdir -p artifacts
@mv cloudflared artifacts/cloudflared
.PHONY: ci-fips-build
ci-fips-build:
@FIPS=true GOOS=linux GOARCH=amd64 $(MAKE) cloudflared
@mkdir -p artifacts
@mv cloudflared artifacts/cloudflared
.PHONY: ci-test
ci-test: fmt-check lint test
@go run -mod=readonly github.com/jstemmer/go-junit-report/v2@latest -in $(GO_TEST_LOG_OUTPUT) -parser gojson -out report.xml -set-exit-code
.PHONY: ci-fips-test
ci-fips-test:
@FIPS=true $(MAKE) ci-test

View File

@ -9,7 +9,7 @@ import pytest
import test_logging import test_logging
from conftest import CfdModes from conftest import CfdModes
from util import select_platform, start_cloudflared, wait_tunnel_ready, write_config from util import select_platform, skip_on_ci, start_cloudflared, wait_tunnel_ready, write_config
def default_config_dir(): def default_config_dir():
@ -82,6 +82,7 @@ class TestServiceMode:
os.remove(default_config_file()) os.remove(default_config_file())
self.launchctl_cmd("list", success=False) self.launchctl_cmd("list", success=False)
@skip_on_ci("we can't run sudo command on CI")
@select_platform("Linux") @select_platform("Linux")
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"), @pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
reason=f"There is already a config file in default path") reason=f"There is already a config file in default path")
@ -98,6 +99,7 @@ class TestServiceMode:
self.sysv_service_scenario(config, tmp_path, assert_log_file) self.sysv_service_scenario(config, tmp_path, assert_log_file)
@skip_on_ci("we can't run sudo command on CI")
@select_platform("Linux") @select_platform("Linux")
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"), @pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
reason=f"There is already a config file in default path") reason=f"There is already a config file in default path")
@ -116,6 +118,7 @@ class TestServiceMode:
self.sysv_service_scenario(config, tmp_path, assert_rotating_log) self.sysv_service_scenario(config, tmp_path, assert_rotating_log)
@skip_on_ci("we can't run sudo command on CI")
@select_platform("Linux") @select_platform("Linux")
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"), @pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
reason=f"There is already a config file in default path") reason=f"There is already a config file in default path")

View File

@ -10,7 +10,6 @@ import pytest
import requests import requests
import yaml import yaml
import json
from retrying import retry from retrying import retry
from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS
@ -35,6 +34,12 @@ def fips_enabled():
nofips = pytest.mark.skipif( nofips = pytest.mark.skipif(
fips_enabled(), reason=f"Only runs without FIPS (COMPONENT_TESTS_FIPS=0)") fips_enabled(), reason=f"Only runs without FIPS (COMPONENT_TESTS_FIPS=0)")
def skip_on_ci(reason):
env_ci = os.getenv("CI")
running_in_ci = env_ci is not None and env_ci != "0"
return pytest.mark.skipif(
running_in_ci, reason=f"This test can't run on CI due to: {reason}")
def write_config(directory, config): def write_config(directory, config):
config_path = directory / "config.yml" config_path = directory / "config.yml"
with open(config_path, 'w') as outfile: with open(config_path, 'w') as outfile: