Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,33 @@ docs: $(REFERENCE_DOC_GENERATOR) ## Generate API reference documentation.
@echo "Generating API doc..."
@$(REFERENCE_DOC_GENERATOR) --source-path=api/v1alpha1/ --renderer=markdown --config=hack/docs/config.yaml --output-path=docs/api_reference.md

##@ E2E Coverage
##
## Targets for building a coverage-instrumented operator image, collecting
## coverage data written during E2E tests, and uploading the report to Codecov.
##
## Typical flow (local):
## make docker-build-coverage docker-push-coverage # build & push coverage image
## COVERAGE_IMAGE=<pullspec> hack/e2e-coverage.sh setup # patch CSV
## make test-e2e # run E2E suite
## make e2e-coverage-collect # collect + upload
##
## In CI, hack/e2e-coverage.sh handles setup and collection automatically.

COVERAGE_IMG ?= $(IMG)-e2e-coverage

.PHONY: docker-build-coverage
docker-build-coverage: ## Build coverage Docker image from images/ci/Dockerfile.coverage.
$(CONTAINER_TOOL) build -f images/ci/Dockerfile.coverage -t $(COVERAGE_IMG) .

.PHONY: docker-push-coverage
docker-push-coverage: ## Push coverage Docker image.
$(CONTAINER_TOOL) push $(COVERAGE_IMG)

.PHONY: e2e-coverage-collect
e2e-coverage-collect: ## Collect e2e coverage data and optionally upload to Codecov.
ARTIFACT_DIR=$${ARTIFACT_DIR:-.} hack/e2e-coverage.sh collect

.PHONY: clean
clean: ## Clean up generated files and directories.
@echo "Cleaning up make generated files...."
Expand Down
163 changes: 163 additions & 0 deletions hack/e2e-coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#!/usr/bin/env bash
#
# E2E coverage lifecycle script for CI and local use.
#
# Usage:
# hack/e2e-coverage.sh setup Prepare the operator for coverage collection
# hack/e2e-coverage.sh collect Collect, convert, and optionally upload coverage data
#
# Environment variables:
# COVERAGE_IMAGE (setup) Full pullspec of the coverage-instrumented image
# CODECOV_TOKEN (collect) Codecov upload token; skip upload if unset
# ARTIFACT_DIR (collect) Directory for CI artifacts; defaults to "."
set -euo pipefail

NAMESPACE="external-secrets-operator"
DEPLOYMENT="external-secrets-operator-controller-manager"
GOCOVERDIR_PATH="/tmp/e2e-cover"
CODECOV_SECRET_PATH="/var/run/secrets/codecov/CODECOV_TOKEN"
POD_LABEL="app=external-secrets-operator"

setup() {
echo "--- E2E Coverage Setup ---"

if [[ -z "${COVERAGE_IMAGE:-}" ]]; then
echo "Error: COVERAGE_IMAGE env var must be set"
exit 1
fi
echo "Coverage image: ${COVERAGE_IMAGE}"

echo "Discovering CSV from deployment ownerReference..."
local csv
csv=$(oc get deployment "${DEPLOYMENT}" -n "${NAMESPACE}" \
-o jsonpath='{.metadata.ownerReferences[?(@.kind=="ClusterServiceVersion")].name}')
if [[ -z "${csv}" ]]; then
echo "Error: no CSV found for external-secrets-operator"
exit 1
fi
echo "Found CSV: ${csv}"

echo "Patching CSV with coverage image, GOCOVERDIR, and emptyDir volume..."
oc patch csv "${csv}" -n "${NAMESPACE}" --type=json -p "[
{\"op\": \"replace\", \"path\": \"/spec/install/spec/deployments/0/spec/template/spec/containers/0/image\", \"value\": \"${COVERAGE_IMAGE}\"},
{\"op\": \"add\", \"path\": \"/spec/install/spec/deployments/0/spec/template/spec/containers/0/env/-\", \"value\": {\"name\": \"GOCOVERDIR\", \"value\": \"${GOCOVERDIR_PATH}\"}},
{\"op\": \"add\", \"path\": \"/spec/install/spec/deployments/0/spec/template/spec/containers/0/volumeMounts/-\", \"value\": {\"name\": \"coverage-data\", \"mountPath\": \"${GOCOVERDIR_PATH}\"}},
{\"op\": \"add\", \"path\": \"/spec/install/spec/deployments/0/spec/template/spec/volumes/-\", \"value\": {\"name\": \"coverage-data\", \"emptyDir\": {}}}
]"
Comment thread
siddhibhor-56 marked this conversation as resolved.

echo "Waiting for operator rollout with coverage image..."
oc rollout status "deployment/${DEPLOYMENT}" -n "${NAMESPACE}" --timeout=180s

echo "Verifying GOCOVERDIR is set in the running pod..."
oc exec -n "${NAMESPACE}" "deploy/${DEPLOYMENT}" -- env | grep GOCOVERDIR || \
echo "Warning: GOCOVERDIR not found in pod env (non-fatal)"

echo "--- Coverage setup complete ---"
}

collect() {
echo "--- E2E Coverage Collection ---"

local artifact_dir="${ARTIFACT_DIR:-.}"
local coverage_dir="${artifact_dir}/e2e-cover-data"
local coverage_profile="${artifact_dir}/coverage-e2e.out"

if [[ -z "${CODECOV_TOKEN:-}" ]] && [[ -f "${CODECOV_SECRET_PATH}" ]]; then
CODECOV_TOKEN=$(cat "${CODECOV_SECRET_PATH}")
export CODECOV_TOKEN
fi

local pod
pod=$(oc get pod -n "${NAMESPACE}" -l "${POD_LABEL}" \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [[ -z "${pod}" ]]; then
echo "Error: no operator pod found in namespace ${NAMESPACE}"
exit 1
fi
echo "Operator pod: ${pod}"

echo "Sending SIGTERM to operator process to flush coverage data..."
oc exec -n "${NAMESPACE}" "${pod}" -c manager -- /bin/sh -c 'kill -TERM 1' || true

echo "Waiting for container to restart..."
oc wait pod/"${pod}" --for=condition=Ready=False -n "${NAMESPACE}" --timeout=30s 2>/dev/null || true
oc wait pod/"${pod}" --for=condition=Ready -n "${NAMESPACE}" --timeout=120s

mkdir -p "${coverage_dir}"
echo "Copying coverage data from operator pod..."
oc cp "${NAMESPACE}/${pod}:${GOCOVERDIR_PATH}/." "${coverage_dir}" -c manager

echo "Coverage files:"
ls -la "${coverage_dir}/" 2>/dev/null || true

if ls "${coverage_dir}"/covmeta.* >/dev/null 2>&1; then
echo "Converting coverage data to Go profile format..."
go tool covdata textfmt -i="${coverage_dir}" -o="${coverage_profile}"

echo ""
echo "=== E2E Coverage Summary ==="
go tool covdata percent -i="${coverage_dir}"
echo "============================="
echo ""
echo "Coverage profile: ${coverage_profile} ($(wc -l < "${coverage_profile}") lines)"

if [[ -n "${CODECOV_TOKEN:-}" ]]; then
echo "Uploading to Codecov..."
local codecov_version="v0.8.0"
local codecov_bin="${artifact_dir}/codecov"
curl -sS -o "${codecov_bin}" "https://uploader.codecov.io/${codecov_version}/linux/codecov"
curl -sS -o "${codecov_bin}.SHA256SUM" "https://uploader.codecov.io/${codecov_version}/linux/codecov.SHA256SUM"

cd "$(dirname "${codecov_bin}")" && sha256sum -c "$(basename "${codecov_bin}").SHA256SUM" && cd - >/dev/null
chmod +x "${codecov_bin}"

local -a codecov_args=(
--file="${coverage_profile}"
--flags=e2e
--name="E2E Coverage"
--verbose
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need it to be verbose, if so, please make sure, token is not leaked in the logs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked on the logs the token is not getting leaked, we can keep verbose for better debuggability.

)

local job_type="${JOB_TYPE:-local}"
if [[ "${job_type}" == "presubmit" ]]; then
echo "Detected presubmit (PR #${PULL_NUMBER:-unknown})"
[[ -n "${PULL_NUMBER:-}" ]] && codecov_args+=(--pr "${PULL_NUMBER}")
[[ -n "${PULL_PULL_SHA:-}" ]] && codecov_args+=(--sha "${PULL_PULL_SHA}")
[[ -n "${PULL_BASE_REF:-}" ]] && codecov_args+=(--branch "${PULL_BASE_REF}")
[[ -n "${REPO_OWNER:-}" && -n "${REPO_NAME:-}" ]] && codecov_args+=(--slug "${REPO_OWNER}/${REPO_NAME}")
elif [[ "${job_type}" == "postsubmit" ]]; then
echo "Detected postsubmit (branch ${PULL_BASE_REF:-unknown})"
[[ -n "${PULL_BASE_SHA:-}" ]] && codecov_args+=(--sha "${PULL_BASE_SHA}")
[[ -n "${PULL_BASE_REF:-}" ]] && codecov_args+=(--branch "${PULL_BASE_REF}")
[[ -n "${REPO_OWNER:-}" && -n "${REPO_NAME:-}" ]] && codecov_args+=(--slug "${REPO_OWNER}/${REPO_NAME}")
else
echo "Local run -- no Prow context, Codecov will auto-detect from git"
fi

"${codecov_bin}" "${codecov_args[@]}" || echo "Warning: Codecov upload failed (non-fatal)"
rm -f "${codecov_bin}" "${codecov_bin}.SHA256SUM"
else
echo "CODECOV_TOKEN not set -- skipping Codecov upload."
echo "Coverage profile saved as artifact: ${coverage_profile}"
fi
else
echo "Warning: No coverage data found in ${coverage_dir}"
echo "The operator may not have been built with coverage instrumentation,"
echo "or it may not have exited cleanly (SIGKILL instead of SIGTERM)."
fi

echo "--- Coverage collection complete ---"
}

case "${1:-}" in
setup)
setup
;;
collect)
collect
;;
*)
echo "Usage: $0 {setup|collect}" >&2
exit 1
;;
esac
4 changes: 3 additions & 1 deletion hack/govulncheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ set -o errexit
## Below vulnerabilities are in the go packages, which impacts the operator code and requires the fix to be available downstream.
# - https://pkg.go.dev/vuln/GO-2026-4601 - Incorrect parsing of IPv6 host literals in net/url
# - https://pkg.go.dev/vuln/GO-2026-4602 - FileInfo can escape from a Root in os
KNOWN_VULNS_PATTERN="GO-2025-3521|GO-2025-3547|GO-2026-4601|GO-2026-4602"
# - https://pkg.go.dev/vuln/GO-2026-4971 - Dial and LookupPort panic on Windows with NUL input in net
# - https://pkg.go.dev/vuln/GO-2026-4918 - HTTP/2 infinite loop via SETTINGS_MAX_FRAME_SIZE of 0 in net/http, golang.org/x/net
KNOWN_VULNS_PATTERN="GO-2025-3521|GO-2025-3547|GO-2026-4971|GO-2026-4918"

GOVULNCHECK_BIN="${1:-}"
OUTPUT_DIR="${2:-}"
Expand Down
27 changes: 27 additions & 0 deletions images/ci/Dockerfile.coverage
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Build the external-secrets-operator binary with coverage instrumentation.
# This mirrors images/ci/Dockerfile but adds Go coverage flags so the binary
# records which lines are executed during E2E tests.
FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.21 AS builder

ARG SRC_DIR=/go/src/github.com/openshift/external-secrets-operator
ENV GO_BUILD_TAGS=strictfipsruntime,openssl
ENV GOEXPERIMENT=strictfipsruntime
ENV CGO_ENABLED=1
ENV GOFLAGS=""

WORKDIR $SRC_DIR

COPY . .

RUN go build -tags $GO_BUILD_TAGS \
-cover -covermode=count -coverpkg=./... \
-o external-secrets-operator cmd/external-secrets-operator/main.go

FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
RUN microdnf install -y tar && microdnf clean all
ARG SRC_DIR=/go/src/github.com/openshift/external-secrets-operator
COPY --from=builder $SRC_DIR/external-secrets-operator /bin/external-secrets-operator
RUN mkdir -p /tmp/e2e-cover && chown 65534:65534 /tmp/e2e-cover && chmod 700 /tmp/e2e-cover
USER 65534:65534
ENV GOCOVERDIR=/tmp/e2e-cover
ENTRYPOINT ["/bin/external-secrets-operator"]