diff --git a/.ci/ci-image.gitlab-ci.yml b/.ci/ci-image.gitlab-ci.yml index ea5a8623..e5202586 100644 --- a/.ci/ci-image.gitlab-ci.yml +++ b/.ci/ci-image.gitlab-ci.yml @@ -8,10 +8,9 @@ include: inputs: stage: pre-build jobPrefix: ci-image - # runOnChangesTo: [".ci/image/**"] - # runOnMR: true - # runOnBranches: '^master$' - runOnBranches: "^.+$" + runOnChangesTo: [".ci/image/**"] + runOnMR: true + runOnBranches: '^master$' commentImageRefs: false runner: vm-linux-x86-4cpu-8gb EXTRA_DIB_ARGS: "--manifest=.ci/image/.docker-images" @@ -23,9 +22,8 @@ include: inputs: stage: pre-build jobPrefix: ci-image - # runOnMR: true - # runOnBranches: '^master$' - runOnBranches: "^.+$" + runOnMR: true + runOnBranches: '^master$' IMAGE_PATH: "$REGISTRY_HOST/stash/tun/cloudflared/ci-image/master" VARIABLE_NAME: BUILD_IMAGE needs: diff --git a/.ci/commons.gitlab-ci.yml b/.ci/commons.gitlab-ci.yml index 7eea1e5e..990c5709 100644 --- a/.ci/commons.gitlab-ci.yml +++ b/.ci/commons.gitlab-ci.yml @@ -5,13 +5,19 @@ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: always - when: never - # Rules to run the job only on branches that are not master. This is needed because for now - # we need to keep a similar behavior due to the integration with teamcity, which requires us - # to not trigger pipelines on tags and/or merge requests. - run-on-branch: + # Rules to run the job only on merge requests + run-on-mr: - if: $CI_COMMIT_TAG 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: never @@ -28,4 +34,20 @@ else echo "No tag present — skipping." exit 0 - fi \ No newline at end of file + 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: {} diff --git a/.ci/image/Dockerfile b/.ci/image/Dockerfile index 28513023..4e92860e 100644 --- a/.ci/image/Dockerfile +++ b/.ci/image/Dockerfile @@ -7,8 +7,9 @@ RUN apt-get update && \ apt-get install --no-install-recommends --allow-downgrades -y \ build-essential \ git \ - go-boring=1.24.4-1 \ + go-boring=1.24.6-1 \ libffi-dev \ + procps \ python3-dev \ python3-pip \ python3-setuptools \ diff --git a/.ci/linux.gitlab-ci.yml b/.ci/linux.gitlab-ci.yml new file mode 100644 index 00000000..e1104145 --- /dev/null +++ b/.ci/linux.gitlab-ci.yml @@ -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 diff --git a/.ci/mac.gitlab-ci.yml b/.ci/mac.gitlab-ci.yml index 1fc433de..94e16fbd 100644 --- a/.ci/mac.gitlab-ci.yml +++ b/.ci/mac.gitlab-ci.yml @@ -6,7 +6,7 @@ include: ############################### .mac-build-defaults: &mac-build-defaults rules: - - !reference [.default-rules, run-on-branch] + - !reference [.default-rules, run-on-mr] tags: - "macstadium-${RUNNER_ARCH}" parallel: diff --git a/.ci/scripts/component-tests.sh b/.ci/scripts/component-tests.sh new file mode 100755 index 00000000..68abbf1d --- /dev/null +++ b/.ci/scripts/component-tests.sh @@ -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 diff --git a/fmt-check.sh b/.ci/scripts/fmt-check.sh similarity index 54% rename from fmt-check.sh rename to .ci/scripts/fmt-check.sh index 31fc7abc..4c1cbad0 100755 --- a/fmt-check.sh +++ b/.ci/scripts/fmt-check.sh @@ -1,8 +1,7 @@ #!/bin/bash - 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 PAGER=$(which colordiff || echo cat) diff --git a/.ci/scripts/windows/component-test.ps1 b/.ci/scripts/windows/component-test.ps1 index abf024ff..dea9e115 100644 --- a/.ci/scripts/windows/component-test.ps1 +++ b/.ci/scripts/windows/component-test.ps1 @@ -31,7 +31,7 @@ Write-Host "Running component tests" try { python -m pip --disable-pip-version-check install --upgrade -r component-tests/requirements.txt --use-pep517 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) { throw "Failed component tests" } diff --git a/.ci/scripts/windows/go-wrapper.ps1 b/.ci/scripts/windows/go-wrapper.ps1 index 217553a3..8eca1ae8 100644 --- a/.ci/scripts/windows/go-wrapper.ps1 +++ b/.ci/scripts/windows/go-wrapper.ps1 @@ -3,7 +3,7 @@ Param( [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 # version in the path. diff --git a/.ci/windows.gitlab-ci.yml b/.ci/windows.gitlab-ci.yml index 256c7ff4..fdc5ac0a 100644 --- a/.ci/windows.gitlab-ci.yml +++ b/.ci/windows.gitlab-ci.yml @@ -6,7 +6,7 @@ include: ################################### .windows-build-defaults: &windows-build-defaults rules: - - !reference [.default-rules, run-on-branch] + - !reference [.default-rules, run-always] tags: - windows-x86 cache: {} @@ -27,27 +27,20 @@ build-cloudflared-windows: ### Load Environment Variables for Component Tests ### ###################################################### load-windows-env-variables: - rules: - - !reference [.default-rules, run-on-branch] stage: pre-build + extends: .component-tests script: - - echo "COMPONENT_TESTS_CONFIG=component-test-config.yaml" >> windows.env - - echo "COMPONENT_TESTS_CONFIG_CONTENT=Y2xvdWRmbGFyZWRfYmluYXJ5OiBjbG91ZGZsYXJlZC5leGUKY3JlZGVudGlhbHNfZmlsZTogY3JlZC5qc29uCm9yaWdpbmNlcnQ6IGNlcnQucGVtCnpvbmVfZG9tYWluOiBhcmdvdHVubmVsdGVzdC5jb20Kem9uZV90YWc6IDQ4Nzk2ZjFlNzBiYjc2NjljMjliYjUxYmEyODJiZjY1" >> windows.env + - echo "COMPONENT_TESTS_CONFIG=$COMPONENT_TESTS_CONFIG" >> windows.env + - echo "COMPONENT_TESTS_CONFIG_CONTENT=$COMPONENT_TESTS_CONFIG_CONTENT" >> 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 - echo "COMPONENT_TESTS_ORIGINCERT=$(echo "$COMPONENT_TESTS_ORIGINCERT" | base64 -w0)" >> windows.env - 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 + variables: + COMPONENT_TESTS_CONFIG_CONTENT: Y2xvdWRmbGFyZWRfYmluYXJ5OiBjbG91ZGZsYXJlZC5leGUKY3JlZGVudGlhbHNfZmlsZTogY3JlZC5qc29uCm9yaWdpbmNlcnQ6IGNlcnQucGVtCnpvbmVfZG9tYWluOiBhcmdvdHVubmVsdGVzdC5jb20Kem9uZV90YWc6IDQ4Nzk2ZjFlNzBiYjc2NjljMjliYjUxYmEyODJiZjY1 artifacts: access: 'none' reports: dotenv: windows.env - cache: {} ################################### ### 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 - $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" + artifacts: + reports: + junit: report.xml ################################ ### Package Windows Binaries ### diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a4ea2864..09826a2c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,27 +1,13 @@ variables: - # Define GOPATH within the project directory to allow GitLab CI to cache it. - # By default, Go places modules in GOMODCACHE, often outside the project. - # 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}/ + GO_VERSION: "go1.24.6" + GIT_DEPTH: "0" default: id_tokens: VAULT_ID_TOKEN: aud: https://vault.cfdata.org -stages: [pre-build, build, test, package, release] +stages: [pre-build, build, validate, test, package, release] include: ##################################################### @@ -34,6 +20,11 @@ include: ##################################################### - local: .ci/ci-image.gitlab-ci.yml + ##################################################### + ################## Linux Builds ################### + ##################################################### + - local: .ci/linux.gitlab-ci.yml + ##################################################### ################## Windows Builds ################### ##################################################### @@ -48,27 +39,3 @@ include: ################# Release Packages ################## ##################################################### - 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 diff --git a/Makefile b/Makefile index fa694c94..96271ae7 100644 --- a/Makefile +++ b/Makefile @@ -128,6 +128,8 @@ endif #for FIPS compliance, FPM defaults to MD5. RPM_DIGEST := --rpm-digest sha256 +GO_TEST_LOG_OUTPUT = /tmp/gotest.log + .PHONY: all all: cloudflared test @@ -137,7 +139,7 @@ clean: .PHONY: vulncheck vulncheck: - @govulncheck ./... + @go run -mod=readonly golang.org/x/vuln/cmd/govulncheck@latest ./... .PHONY: cloudflared cloudflared: @@ -160,11 +162,9 @@ generate-docker-version: .PHONY: test test: vet -ifndef CI - go test -v -mod=vendor -race $(LDFLAGS) ./... -else - @mkdir -p .cover - go test -v -mod=vendor -race $(LDFLAGS) -coverprofile=".cover/c.out" ./... + $Q go test -json -v -mod=vendor -race $(LDFLAGS) ./... 2>&1 | tee $(GO_TEST_LOG_OUTPUT) +ifneq ($(FIPS), true) + @go run -mod=readonly github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest -input $(GO_TEST_LOG_OUTPUT) endif .PHONY: cover @@ -254,7 +254,7 @@ capnp: .PHONY: vet vet: - go vet -mod=vendor github.com/cloudflare/cloudflared/... + $Q go vet -mod=vendor github.com/cloudflare/cloudflared/... .PHONY: fmt fmt: @@ -263,7 +263,7 @@ fmt: .PHONY: fmt-check fmt-check: - @./fmt-check.sh + @./.ci/scripts/fmt-check.sh .PHONY: lint lint: @@ -272,3 +272,23 @@ lint: .PHONY: mocks mocks: 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 diff --git a/component-tests/test_service.py b/component-tests/test_service.py index e1c155e6..e992be33 100644 --- a/component-tests/test_service.py +++ b/component-tests/test_service.py @@ -9,7 +9,7 @@ import pytest import test_logging 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(): @@ -82,6 +82,7 @@ class TestServiceMode: os.remove(default_config_file()) self.launchctl_cmd("list", success=False) + @skip_on_ci("we can't run sudo command on CI") @select_platform("Linux") @pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"), 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) + @skip_on_ci("we can't run sudo command on CI") @select_platform("Linux") @pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"), 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) + @skip_on_ci("we can't run sudo command on CI") @select_platform("Linux") @pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"), reason=f"There is already a config file in default path") diff --git a/component-tests/util.py b/component-tests/util.py index c7fd59c4..b4c9b51b 100644 --- a/component-tests/util.py +++ b/component-tests/util.py @@ -10,7 +10,6 @@ import pytest import requests import yaml -import json from retrying import retry from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS @@ -35,6 +34,12 @@ def fips_enabled(): nofips = pytest.mark.skipif( 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): config_path = directory / "config.yml" with open(config_path, 'w') as outfile: