Skip to content
Skillv1.0.0

coverport-integration

Integrate coverport into repositories to enable e2e test coverage collection and upload to Codecov. Supports Go, Python, Node.js, and Rust applications with Tekton/Konflux pipelines and GitHub Actions

by konflux-ci(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from konflux-ci/coverport (.claude/skills/coverport-integration/SKILL.md). Install upstream with npx skills add konflux-ci/coverport --skill coverport-integration. Copyright stays with the author.

Coverport Integration Skill

This skill automates the integration of coverport into repositories for e2e test coverage collection and upload to Codecov. It supports both Tekton/Konflux pipelines and GitHub Actions workflows.

What is Coverport?

Coverport is a tool that enables e2e test coverage collection by:

  1. Building instrumented container images (Go with -cover, Python with coverage wrapper, Node.js with V8 inspector, Rust with -C instrument-coverage)
  2. Collecting coverage data from running containers during e2e tests — via HTTP endpoint or from test runner output
  3. Processing and uploading the coverage data to Codecov with appropriate flags

The coverport CLI is available as a container image that includes all dependencies (oras, cosign, git, language-specific tools):

quay.io/konflux-ci/konflux-devprod/coverport-cli:<tag>

IMPORTANT — Pin the image tag: Never use :latest or an untagged image in generated CI workflows. Always resolve the current latest tag at onboarding time (see Step 0) and pin to it. This prevents breaking the onboarded application's CI if coverport introduces breaking changes later.

When to Use This Skill

Use this skill when the user:

  • Asks to integrate coverport into their repository
  • Wants to add e2e test coverage tracking
  • Needs to set up coverage instrumentation for Go, Python, Node.js, or Rust projects
  • Mentions integrating coverage collection for Tekton/Konflux pipelines
  • Wants to collect e2e coverage in GitHub Actions using the coverport CLI container
  • Mentions LLVM coverage, profraw, or instrument-coverage for Rust

Prerequisites

Before using this skill, verify the repository has:

  • Codebase with a Dockerfile (Go, Python, Node.js, or Rust)
  • One of the following CI/CD setups:
    • Tekton pipelines (typically in .tekton/ directory) with an E2E test pipeline (typically in integration-tests/pipelines/)
    • GitHub Actions workflows (in .github/workflows/)
  • Codecov account (see codecov-config/CONFIG.md for instance routing)

Instructions

Step 0: Pre-Integration Repository Scan

Before starting, run these checks to understand the repository structure:

  1. Resolve the current coverport-cli image tag:

    # Fetch the latest git commit SHA tag from Quay (no auth needed for public repos)
    COVERPORT_TAG=$(curl -s "https://quay.io/api/v1/repository/konflux-ci/konflux-devprod%2Fcoverport-cli/tag/?onlyActiveTags=true&limit=50" \
      | jq -r '
        (.tags[] | select(.name == "latest") | .manifest_digest) as $digest |
        .tags[] | select(.manifest_digest == $digest and (.name | test("^[0-9a-f]{40}$"))) | .name
      ' | head -1)
    echo "Pinning to: quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG}"

    Use this tag in all generated CI YAML instead of :latest or untagged references. If the API is unreachable or jq is not available, fall back to using the latest tag with a comment noting it should be pinned.

  2. Detect project language and entry point:

    # Go projects
    find . -name "main.go" -not -path "*/vendor/*" -not -path "*/test/*"
    # Python projects
    find . -name "requirements.txt" -o -name "setup.py" -o -name "pyproject.toml" | head -5
    ls *.py Dockerfile Containerfile 2>/dev/null
    # Rust projects
    find . -name "Cargo.toml" -not -path "*/target/*" | head -10
    grep -r "\[\[bin\]\]" Cargo.toml */Cargo.toml 2>/dev/null
    ls src/main.rs 2>/dev/null
  3. Check current Dockerfile build command:

    # Go projects
    grep -A5 "go build" Dockerfile Containerfile 2>/dev/null
    # Python projects
    grep -A5 "pip install\|ENTRYPOINT\|CMD" Dockerfile Containerfile 2>/dev/null
    # Rust projects
    grep -A5 "cargo build" Dockerfile Containerfile 2>/dev/null
  4. List Tekton pipelines:

    ls .tekton/*.yaml
    ls integration-tests/pipelines/*.yaml 2>/dev/null || echo "No integration-tests/pipelines found"
  5. Check for existing coverage setup:

    grep -r "ENABLE_COVERAGE\|instrumented\|coverport\|instrument-coverage\|coverage-server" . --exclude-dir=vendor --exclude-dir=target --exclude-dir=.git
  6. Check if e2e test suite rebuilds the container image:

    grep -r "docker-build\|docker build\|podman build\|make.*build.*IMG" test/ --include="*.go" 2>/dev/null

    This is common in kubebuilder/operator-sdk projects where the Ginkgo BeforeSuite rebuilds and loads the image into Kind. If found, the test code must pass ENABLE_COVERAGE through to the build command, otherwise it will overwrite the instrumented image with a production one.

  7. Determine where e2e tests run:

    # Check for Tekton integration test pipelines
    ls integration-tests/pipelines/*.yaml 2>/dev/null || echo "No Tekton e2e pipelines"
    # Check for GitHub Actions e2e workflows
    grep -rl "e2e\|integration" .github/workflows/ --include="*.yml" --include="*.yaml" 2>/dev/null

    This is critical for deciding which pipeline changes are needed (see Decision Point below).

  8. Determine Python e2e test style (Python projects only):

    # Does the pipeline run pytest against cloned source?
    grep -A5 "pytest\|python.*run_tests" integration-tests/pipelines/*.yaml 2>/dev/null
    # Does the pipeline deploy or test a container image?
    grep -rl "kind\|kubectl\|helm\|deploy\|instrumented-container" integration-tests/pipelines/*.yaml .github/workflows/ 2>/dev/null

    Two distinct Python paths — choose based on what the e2e tests actually exercise:

    • Pattern D (pytest-cov): Pipeline clones the repo, installs dependencies, and runs pytest directly against source code. The container image is NOT deployed or tested. No container instrumentation needed.
    • Container instrumentation (Steps 3-5 Python): E2e tests deploy and exercise the container image (K8s/Kind, podman run, etc.). Vendor the files from instrumentation/python/ and collect via Pattern A or B on port 53700. Do NOT route Python container deployments to Pattern D.

This helps identify potential conflicts or existing coverage infrastructure before making changes.

Step 1: Analyze the Repository

Analyze the repository structure to understand what needs to be modified:

  1. Find the Dockerfile - Look for the main Dockerfile
  2. Identify binaries being built - Check what Go binaries are compiled in the Dockerfile and note if main.go is in root or subdirectory
  3. Find Tekton push pipeline - Look in .tekton/ for *-push.yaml
  4. Find E2E test pipeline - Look in integration-tests/pipelines/ for *e2e*.yaml
  5. Find Tekton PR pipeline - Look in .tekton/ for *-pull-request.yaml
  6. Find GitHub Actions - Look in .github/workflows/ for pr.yaml, pr.yml, codecov.yaml, or codecov.yml
  7. Check for existing coverage integration - Search for ENABLE_COVERAGE, instrumented, coverport
  8. Determine where e2e tests run - This determines which pipeline changes are needed:
    • Tekton integration pipelines (integration-tests/pipelines/): Needs instrumented image in Tekton push pipeline + coverage collection task in e2e pipeline
    • GitHub Actions only (e.g., Kind cluster in .github/workflows/): Only needs Dockerfile + GitHub Actions changes, no Tekton pipeline changes
    • Both: Apply both sets of changes

Step 2: Ask Clarifying Questions

Before making changes, ask the user:

  1. Which binaries to instrument? (Go/Rust) - If the Dockerfile builds multiple binaries, ask which ones run during e2e tests
  2. Python: container vs source tests? (Python) - Do e2e tests deploy the container image, or run pytest directly against source? Container deployment → Steps 3-5 (Python); pytest against source → Pattern D
  3. Python: WSGI entry point (Python container path) - Confirm the Gunicorn module path (e.g. app:app) and container WORKDIR (typically /app, must match .coveragerc source)
  4. Tenant namespace - Confirm the namespace where their build and integration pipelines run (check .tekton/*-push.yaml for the namespace field)
  5. Secret name - Confirm they want to use coverport-secrets or specify a different name
  6. OCI storage - Confirm where coverage data should be stored (the quay.io repository for test artifacts)

Decision Point: Tekton vs GitHub Actions

After Steps 0-2, determine which changes to apply based on where e2e tests run:

E2E tests run in... Test style Apply these steps
Tekton integration pipelines only (integration-tests/pipelines/) Go/Rust: tests deploy/use the container image Steps 3-6, E2E pipeline update, Step 8 (Tekton PR)
Tekton integration pipelines only (integration-tests/pipelines/) Python: tests run pytest directly against source Pattern D: pytest-cov — no container instrumentation needed
Tekton integration pipelines only (integration-tests/pipelines/) Python: app deployed as container (Flask/Django/FastAPI + Gunicorn) Steps 3-5 (Python), Step 6 (Python), E2E pipeline update, Step 8 (Python PR)not Pattern D
GitHub Actions only (no integration-tests/pipelines/) Go/Rust: tests deploy/use the container image Steps 3-5.5, Step 7 (GitHub Actions) — skip Steps 6 and 8
GitHub Actions only Python: app deployed as container (Kind, podman run, etc.) Steps 3-5 (Python), Step 7 Pattern A (Kind) preferred; Pattern B only for local podman run
Both Tekton and GitHub Actions Tests deploy/use the container image All applicable steps (language-specific: Go/Rust use Step 6 ENABLE_COVERAGE; Python uses Step 6 TARGET_STAGE)

Key rule: Do NOT modify Tekton push/PR pipelines (Steps 6, 8) if the repository does not have a Tekton e2e integration pipeline. Building an instrumented image in Tekton is pointless if nothing in Tekton consumes it. The instrumented build happens locally (e.g., via make with ENABLE_COVERAGE=true) in the GitHub Actions workflow instead.

Key rule: Pattern D is only for pytest against cloned source. If Python e2e tests deploy or hit a container image (even when written in pytest), use container instrumentation (Steps 3-5 Python) + Pattern A/B — not Pattern D.

Step 3: Add Coverage Dependency

Go

Add coverport to your Go module dependencies:

go get github.com/konflux-ci/coverport/instrumentation/go
go mod tidy

This will:

  • Add the coverport package to go.mod as a dependency
  • Update go.sum with the dependency checksums

Important: Always run go mod tidy after go get. The go get command adds the dependency as // indirect, but since coverage_init.go imports it directly (even behind a build tag), go mod tidy correctly reclassifies it as a direct dependency. Many CI systems verify that go mod tidy produces no diff.

Rust

Add the coverage-server crate as an optional dependency behind a Cargo feature flag:

[features]
coverage = ["dep:coverage-server"]

[dependencies]
coverage-server = { git = "https://github.com/konflux-ci/coverport.git", subdirectory = "instrumentation/rust", optional = true }

Important:

  • The optional = true ensures coverage-server is only compiled when --features coverage is passed
  • No additional dependencies (like tokio) are needed — the coverage server brings its own runtime
  • The crate is pulled from the coverport monorepo using Cargo's subdirectory parameter

Python

Copy the four instrumentation files from instrumentation/python/ in the coverport monorepo into your application repository. See instrumentation/python/README.md for file descriptions, Dockerfile example, and local validation steps.

instrumentation/python/coverage_server.py  → server/coverage_server.py
instrumentation/python/sitecustomize.py    → server/sitecustomize.py
instrumentation/python/.coveragerc         → server/.coveragerc
instrumentation/python/gunicorn_coverage.py → server/gunicorn_coverage.py

Adjust the destination directory (server/ above) to match your project layout. The Dockerfile in Step 5 must copy from these paths.

Important:

  • No pip install coverport or Go-module equivalent — vendor these files directly
  • Ensure coverage and gunicorn are installed in the instrumented image (add to requirements.txt or install in the Dockerfile test stage)
  • Update source = /app in .coveragerc if your container WORKDIR differs from /app
  • See instrumentation/python/README.md for Dockerfile and validation details

Step 4: Add Coverage Initialization Code

Go

Create a new file coverage_init.go in the root of your Go module (same directory as main.go or where the package main is):

//go:build coverage

package main

// This file is only included when building with -tags=coverage.
// It starts a coverage HTTP server that allows collecting coverage data
// from the running binary during E2E tests.

import _ "github.com/konflux-ci/coverport/instrumentation/go" // starts coverage server via init()

Important:

  • The //go:build coverage tag ensures this file is only included when building with -tags=coverage
  • The blank import triggers the coverage server's init() function
  • This file should be at the root of your Go module (where main.go is, or where the main package is)
  • Always run go mod tidy after this step. The coverport instrumentation dependency was previously fetched as indirect, and the new blank import makes it a direct dependency.

Rust

Add two lines to the application's main() function:

fn main() {
    #[cfg(feature = "coverage")]
    let _coverage = coverage_server::start_coverage_server_standalone(53700);

    // ... rest of the application, completely unchanged ...
}

Important:

  • The #[cfg(feature = "coverage")] attribute ensures this code is only compiled when the feature is enabled
  • start_coverage_server_standalone(53700) spawns the server on its own background thread with its own tokio runtime — no interference with the application's runtime
  • The port (53700) can be overridden at runtime via the COVERAGE_PORT environment variable
  • Works with any Rust application — any async runtime (tokio, async-std, actix-rt) or synchronous apps

Python

No application code changes are required.

Unlike Go (coverage_init.go) or Rust (lines in main()), Python container instrumentation is entirely file-based: sitecustomize.py auto-starts coverage in every process, and gunicorn_coverage.py saves worker data on exit. Skip this step for Python.

Step 5: Modify the Dockerfile

Add build argument (near the top after FROM):

ARG ENABLE_COVERAGE=false

Go

Modify the build command to conditionally build with coverage tags:

RUN if [ "$ENABLE_COVERAGE" = "true" ]; then \
        echo "Building with coverage instrumentation..."; \
        CGO_ENABLED=0 go build -cover -covermode=atomic -tags=coverage -o <binary-name> .; \
    else \
        echo "Building production binary..."; \
        CGO_ENABLED=0 go build -a -o <binary-name> .; \
    fi

Important:

  • Replace <binary-name> with the actual binary name
  • The -tags=coverage flag includes the coverage_init.go file
  • Build the package (.) rather than individual files
  • Only instrument binaries that run during e2e tests
  • Keep other binaries without instrumentation
  • No need to download external files - coverport is now a Go module dependency

Rust

Modify the build command to conditionally build with coverage:

ARG ENABLE_COVERAGE

RUN if [ "$ENABLE_COVERAGE" = "true" ]; then \
        echo "Building with coverage instrumentation"; \
        rustup component add llvm-tools-preview; \
        RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE=/dev/null \
        cargo build --release --features coverage; \
    else \
        echo "Building without coverage (production)"; \
        cargo build --release; \
    fi

Important — Rust-specific details:

  • RUSTFLAGS="-C instrument-coverage" tells the compiler to insert LLVM profiling counters
  • --features coverage activates the coverage-server optional dependency
  • LLVM_PROFILE_FILE=/dev/null suppresses stray .profraw files generated during the build
  • Without -C instrument-coverage, the LLVM FFI symbols won't be present and the build will fail with linker errors if --features coverage is set
  • Without --features coverage, the coverage-server dependency is not included at all — clean production binary

Python

Use a multi-stage Dockerfile to keep production images unchanged. The instrumented test stage installs sitecustomize.py into site-packages, sets coverage environment variables, and wraps the app CMD with coverage_server.py.

  • Local / GitHub Actions: podman build --target test
  • Tekton (buildah-oci-ta): set TARGET_STAGE=test on the instrumented image build task (Step 6 Python) — do not use ENABLE_COVERAGE=true alone; Python has no conditional compile step for that arg
# ... normal build stages (install app deps, COPY application code) ...

FROM base AS production
CMD ["gunicorn", "-b", "0.0.0.0:8080", "-w", "2", "app:app"]

FROM base AS test
RUN pip install --no-cache-dir gunicorn coverage
COPY server/sitecustomize.py /tmp/sitecustomize.py
RUN SITE_PACKAGES=$(python -c "import site; print(site.getsitepackages()[0])") && \
    cp /tmp/sitecustomize.py "$SITE_PACKAGES/sitecustomize.py"
COPY server/.coveragerc /app/.coveragerc
COPY server/gunicorn_coverage.py /opt/gunicorn_coverage.py
COPY server/coverage_server.py /opt/coverage_server.py
ENV COVERAGE_PROCESS_START=/app/.coveragerc
ENV COVERAGE_DATA_DIR=/dev/shm
ENV TMPDIR=/dev/shm
EXPOSE 8080 53700
CMD ["python", "/opt/coverage_server.py", "-m", "gunicorn", \
     "-c", "/opt/gunicorn_coverage.py", "-b", "0.0.0.0:8080", "-w", "1", "app:app"]

Build the instrumented image:

podman build --target test -t myapp:instrumented .

Important — Python-specific details:

  • Replace app:app with your WSGI entry point
  • sitecustomize.py must be installed into site-packages so every Gunicorn worker loads it
  • COVERAGE_DATA_DIR=/dev/shm and TMPDIR=/dev/shm are required for readOnlyRootFilesystem pods
  • coverage_server.py exposes the HTTP coverage endpoint on port 53700 by default (COVERAGE_PORT env var overrides)
  • -w 1 is recommended for initial setup; increase workers once coverage collection is verified
  • See instrumentation/python/README.md for local podman validation and coverport collect examples

Step 5.5: Validate Dockerfile Changes Locally

IMPORTANT: Before proceeding to pipeline changes, validate the Dockerfile modifications work correctly using podman or docker:

# Go/Rust: instrumented build via ENABLE_COVERAGE build arg
podman build --build-arg ENABLE_COVERAGE=true -t test-instrumented -f Dockerfile .

# Python: instrumented build via test stage target
podman build --target test -t test-instrumented -f Dockerfile .

# Production image (without coverage)
podman build -t test-production -f Dockerfile .

# Verify both images built successfully
podman images | grep test-

Expected output in instrumented build:

  • Go/Rust: "Building with coverage instrumentation..."
  • Python: image builds successfully; container logs show [coverage-wrapper] HTTP server listening on port 53700

Expected output in production build:

  • Go: "Building production binary..."
  • Python: production stage CMD runs plain Gunicorn without coverage wrapper

Python validation (after instrumented build):

# Default port is 53700; use the same value as COVERAGE_PORT if your image overrides it
COVERAGE_PORT=53700
podman run --rm -d --name py-cov-test -p 8080:8080 -p ${COVERAGE_PORT}:${COVERAGE_PORT} test-instrumented
curl -s "http://localhost:${COVERAGE_PORT}/health"    # expect coverage_enabled: true
curl -s http://localhost:8080/                        # hit app endpoints to generate coverage
curl -s "http://localhost:${COVERAGE_PORT}/coverage/save"
curl -s "http://localhost:${COVERAGE_PORT}/coverage"  # expect non-empty coverage_data
podman stop py-cov-test

If builds fail:

  • Stop and fix the Dockerfile before proceeding
  • See Troubleshooting section for common issues
  • Go: ensure coverage_init.go exists in the correct location
  • Go: verify Go module dependencies were downloaded (check go.mod and go.sum)
  • Go: check that the build tags syntax is correct in coverage_init.go
  • Python: verify all four instrumentation files were copied and sitecustomize.py is in site-packages
  • Python: confirm gunicorn and coverage packages are installed in the instrumented image

Why this validation matters:

  • Catches Dockerfile syntax errors immediately
  • Verifies coverport Go module integration works
  • Confirms both production and instrumented builds succeed
  • Prevents wasting CI/CD pipeline time on broken builds
  • Validates the conditional build logic works correctly

Step 6: Update Tekton Push Pipeline

Skip this step if e2e tests run only in GitHub Actions. Building an instrumented image in the Tekton push pipeline is only useful when a Tekton integration test pipeline (in integration-tests/pipelines/) consumes it. If e2e tests run exclusively in GitHub Actions (e.g., via Kind cluster), the instrumented build happens locally in the workflow instead.

Add a task to build an instrumented image in the push pipeline (e.g., .tekton/*-push.yaml):

Find the location after prefetch-dependencies task and add:

- name: build-instrumented-image
  params:
  - name: IMAGE
    value: $(params.output-image).instrumented
  - name: DOCKERFILE
    value: $(params.dockerfile)
  - name: CONTEXT
    value: $(params.path-context)
  - name: HERMETIC
    value: $(params.hermetic)
  - name: PREFETCH_INPUT
    value: $(params.prefetch-input)
  - name: IMAGE_EXPIRES_AFTER
    value: $(params.image-expires-after)
  - name: COMMIT_SHA
    value: $(tasks.clone-repository.results.commit)
  - name: BUILD_ARGS
    value:
    - $(params.build-args[*])
    - ENABLE_COVERAGE=true
  - name: BUILD_ARGS_FILE
    value: $(params.build-args-file)
  - name: SOURCE_ARTIFACT
    value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT)
  - name: CACHI2_ARTIFACT
    value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT)
  runAfter:
  - prefetch-dependencies
  taskRef:
    params:
    - name: name
      value: buildah-oci-ta
    - name: bundle
      value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.7@sha256:b54509f5f695c0c89de4587a403099a26da5cdc3707037edd4b7cf4342b63edd
    - name: kind
      value: task
    resolver: bundles
  when:
  - input: $(tasks.init.results.build)
    operator: in
    values:
    - "true"

IMPORTANT - Key points:

  • Use buildah-oci-ta (NOT buildah-remote-oci-ta) - this is a regular local build for amd64 testing clusters
  • This should be a single task, NOT a matrix build (no PLATFORM parameter, no IMAGE_APPEND_PLATFORM)
  • Image tagged with .instrumented suffix
  • HERMETIC: $(params.hermetic) - uses the same hermetic setting as the main build (now supports hermetic builds!)
  • PREFETCH_INPUT: $(params.prefetch-input) - uses the same prefetch settings as the main build
  • BUILD_ARGS includes ENABLE_COVERAGE=true
  • Do NOT add a build-instrumented-image-index task - the instrumented image is single-platform only

Python (Tekton push pipeline)

For Python container apps, the instrumented image is the test Dockerfile stage — not a ENABLE_COVERAGE conditional build. Add the same build-instrumented-image task as above, but use TARGET_STAGE instead of ENABLE_COVERAGE=true:

- name: build-instrumented-image
  params:
  - name: IMAGE
    value: $(params.output-image).instrumented
  - name: DOCKERFILE
    value: $(params.dockerfile)
  - name: CONTEXT
    value: $(params.path-context)
  - name: TARGET_STAGE
    value: test
  - name: HERMETIC
    value: $(params.hermetic)
  - name: PREFETCH_INPUT
    value: $(params.prefetch-input)
  - name: IMAGE_EXPIRES_AFTER
    value: $(params.image-expires-after)
  - name: COMMIT_SHA
    value: $(tasks.clone-repository.results.commit)
  - name: BUILD_ARGS
    value:
    - $(params.build-args[*])
  - name: BUILD_ARGS_FILE
    value: $(params.build-args-file)
  - name: SOURCE_ARTIFACT
    value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT)
  - name: CACHI2_ARTIFACT
    value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT)
  runAfter:
  - prefetch-dependencies
  taskRef:
    params:
    - name: name
      value: buildah-oci-ta
    - name: bundle
      value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.7@sha256:b54509f5f695c0c89de4587a403099a26da5cdc3707037edd4b7cf4342b63edd
    - name: kind
      value: task
    resolver: bundles
  when:
  - input: $(tasks.init.results.build)
    operator: in
    values:
    - "true"

Python Tekton notes:

  • TARGET_STAGE=test maps to podman build --target test (buildah-oci-ta parameter)
  • The test stage name must match the Dockerfile stage defined in Step 5 (Python)
  • Do not add ENABLE_COVERAGE=true to BUILD_ARGS for Python — it has no effect unless you add custom Dockerfile logic to interpret it

Step 5: Update E2E Test Pipeline

Skip this step if e2e tests run only in GitHub Actions. This step applies only when there is a Tekton integration test pipeline in integration-tests/pipelines/.

Make three changes to the e2e test pipeline:

A. Update test-metadata task from v0.3 to v0.4:

- name: test-metadata
  taskRef:
    resolver: git
    params:
      - name: url
        value: https://github.com/konflux-ci/tekton-integration-catalog.git
      - name: revision
        value: main
      - name: pathInRepo
        value: tasks/test-metadata/0.4/test-metadata.yaml

B. Update image references (if applicable):

NOTE: Only modify this if your e2e tests actually run the containerized application.

  • If your tests build the manager from source (e.g., using make build or go run main.go), you may need to modify the build/run commands to use coverage flags instead, or deploy the instrumented container image
  • If your tests deploy and run containers, proceed with updating image references

For tests that deploy/run container images, find parameters that reference images and change:

  • container-repoinstrumented-container-repo
  • container-taginstrumented-container-tag
  • container-imageinstrumented-container-image

Example scenarios:

  • Scenario 1 (uses container): Tests deploy the app to a cluster using the container image → Update image references
  • Scenario 2 (builds from source): Tests run make build && ./manager inside the pipeline → May not need image reference changes, but need to ensure the running process is instrumented
  • Scenario 3 (hybrid): Tests build from source but coverage collection expects instrumented container → Coordinate with user on approach

C. Add coverage collection task after e2e tests:

- name: collect-and-upload-coverage
  runAfter:
    - <e2e-test-task-name>  # Replace with actual task name
  params:
    - name: instrumented-images
      value: "$(tasks.test-metadata.results.instrumented-container-repo):$(tasks.test-metadata.results.instrumented-container-tag)"
    - name: cluster-access-secret-name
      value: kfg-$(context.pipelineRun.name)  # Adjust if different
    - name: test-name
      value: e2e-tests
    - name: oci-container
      value: "$(params.oci-container-repo):$(context.pipelineRun.name)"
    - name: codecov-flags
      value: e2e-tests
    - name: credentials-secret-name
      value: "coverport-secrets"  # Or user-specified name
    # For Rust projects, add:
    # - name: coverage-format
    #   value: rust
  taskRef:
    resolver: git
    params:
      - name: url
        value: https://github.com/konflux-ci/tekton-integration-catalog.git
      - name: revision
        value: main
      - name: pathInRepo
        value: tasks/coverport-coverage/0.1/coverport-coverage.yaml

Rust-specific Tekton note: For Rust projects, add the coverage-format: rust parameter to the coverage collection task. This tells coverport to process the collected profraw data using llvm-profdata and llvm-cov instead of the default Go profile format. The Tekton pipeline YAML is otherwise identical to Go — only this one parameter differs.

Step 8: Update Tekton PR Pipeline (Pull Request Pipeline)

Skip this step if e2e tests run only in GitHub Actions. Adding ENABLE_COVERAGE=true to the Tekton PR build is only useful when the built image is consumed by a Tekton integration test pipeline.

Update the PR pipeline (e.g., .tekton/*-pull-request.yaml) to build with coverage instrumentation:

A. Enable hermetic build and prefetch (if not already enabled):

Add or ensure these parameters exist in the spec.params section:

  - name: hermetic
    value: "true"
  - name: prefetch-input
    value: '{"type": "gomod", "path": "."}'

B. Add ENABLE_COVERAGE=true to BUILD_ARGS:

Find the build-images task (or equivalent) and add ENABLE_COVERAGE=true to its BUILD_ARGS:

- name: build-images
  # ... other params ...
  params:
  # ... other params ...
  - name: BUILD_ARGS
    value:
    - $(params.build-args[*])
    - ENABLE_COVERAGE=true  # Add this line
  # ... rest of the task ...

Key points (Go/Rust):

  • With the Go module approach, hermetic builds are now supported!
  • Enable hermetic: "true" and prefetch-input for secure, reproducible builds
  • Add ENABLE_COVERAGE=true to the regular build task in PR pipeline
  • This enables coverage collection for PR builds which can be used for PR-level testing
  • No need to create a separate instrumented image task in PR pipeline - just modify the existing build task

C. Python PR builds (Step 8 Python):

For Python container apps, set TARGET_STAGE=test on the PR build-images task instead of ENABLE_COVERAGE=true:

- name: build-images
  params:
  # ... other params ...
  - name: TARGET_STAGE
    value: test

If the PR pipeline must still produce a production image for merging, keep the default production build as-is and add a separate instrumented build task (same as Step 6 Python) that tags with .instrumented.

Step 7: Update GitHub Actions

7a: Unit Test Coverage Flags

Add codecov flags to distinguish unit tests from e2e tests.

In .github/workflows/pr.yaml (or similar), update the codecov upload step:

For public repos using app.codecov.io, use OIDC (no token needed):

- name: Upload coverage to Codecov
  uses: codecov/codecov-action@v6
  with:
    use_oidc: true
    flags: unit-tests

The job must have permissions: id-token: write for OIDC to work.

For private repos using a self-hosted Codecov instance, use token auth:

- name: Upload coverage to Codecov
  uses: codecov/codecov-action@v6
  with:
    url: <CODECOV_INSTANCE_URL>
    token: ${{ secrets.CODECOV_TOKEN }}
    flags: unit-tests

See codecov-config/CONFIG.md for the correct Codecov instance URL based on repository location.

7b: E2E Coverage Collection in GitHub Actions

If the repository runs e2e tests in GitHub Actions (not just Tekton), use the coverport CLI container via podman to collect and upload e2e coverage. The container image includes all dependencies (oras, cosign, git, Go, etc.), so nothing needs to be installed separately.

Container image (use the tag resolved in Step 0):

quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG}

There are three patterns depending on where the instrumented app runs and how coverage is collected:

Pattern A: App Running in Kubernetes (HTTP-based collection)

Use when your GitHub Actions workflow deploys the instrumented app to a Kubernetes cluster (e.g., Kind) and runs e2e tests against it. You need kubeconfig access to the cluster.

Important: Rootless podman volume mount permissions on GitHub Actions (Linux):

  • Kubeconfig files typically have 600 permissions. Rootless podman maps container UIDs differently, so the container user cannot read files with 600 permissions. Copy the kubeconfig to a temp file with 644.
  • Output directories must be world-writable (chmod 777) so the container can create subdirectories and write coverage files.
- name: Collect e2e coverage
  if: always()
  run: |
    mkdir -p coverage-output && chmod 777 coverage-output
    cp $HOME/.kube/config /tmp/kubeconfig && chmod 644 /tmp/kubeconfig
    podman run --rm \
      --network host \
      -v /tmp/kubeconfig:/kubeconfig:ro \
      -v $PWD/coverage-output:/workspace/coverage-output \
      -e KUBECONFIG=/kubeconfig \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      collect \
        --namespace=${{ env.TEST_NAMESPACE }} \
        --label-selector=${{ env.LABEL_SELECTOR }} \
        --test-name=e2e-tests \
        --output=/workspace/coverage-output || true

- name: Upload e2e coverage to Codecov
  if: always()
  uses: codecov/codecov-action@v6
  with:
    use_oidc: true  # or token for private repos
    flags: e2e-tests
    files: coverage-output/<component>/<test-name>/coverage.out
    fail_ci_if_error: false

Alternative: Using coverport process + push for OCI storage:

- name: Collect and push e2e coverage
  if: always()
  run: |
    mkdir -p coverage-output && chmod 777 coverage-output
    cp $HOME/.kube/config /tmp/kubeconfig && chmod 644 /tmp/kubeconfig
    podman run --rm \
      --network host \
      -v /tmp/kubeconfig:/kubeconfig:ro \
      -v $PWD/coverage-output:/workspace/coverage-output \
      -e KUBECONFIG=/kubeconfig \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      collect \
        --images=${{ env.INSTRUMENTED_IMAGE }} \
        --namespace=${{ env.TEST_NAMESPACE }} \
        --test-name="e2e-tests" \
        --output=/workspace/coverage-output \
        --push \
        --repository=${{ env.OCI_COVERAGE_REPO }}

    # Process and upload to Codecov
    podman run --rm \
      -e CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      process \
        --artifact-ref=${{ env.COVERAGE_ARTIFACT_REF }} \
        --image=${{ env.INSTRUMENTED_IMAGE }} \
        --codecov-flags=e2e-tests

Key points for Kubernetes collection:

  • --network host is required so coverport can reach the Kind/k8s API
  • The coverport CLI port-forwards to the pod's coverage HTTP endpoint (default 53700 for Go, Python, and Rust instrumentation; CLI also tries 9095 as fallback when --port is omitted)
  • Go: collect generates a coverage.out text profile from binary coverage data
  • Python (K8s path only): collect checks /health, triggers /coverage/save, fetches /coverage, then execs into the pod to run coverage xmlcoverage.xml in the output directory — no separate process step needed
  • Upload Go: coverage-output/.../coverage.out via codecov-action or process
  • Upload Python: coverage-output/<test-name>/coverage.xml via codecov-action
  • You can also use coverport's process command for OCI-based workflows (Go path)
Pattern A (Python): Upload after K8s collect

When collecting Python coverage from Kind/Kubernetes (not --url), upload the XML file generated during collect:

- name: Upload e2e coverage to Codecov
  if: always()
  uses: codecov/codecov-action@v6
  with:
    use_oidc: true
    flags: e2e-tests
    files: coverage-output/e2e-tests/coverage.xml
    fail_ci_if_error: false
Pattern B: App Running Locally via Podman/Docker (HTTP-based collection)

Use when your GitHub Actions workflow starts the instrumented app locally (e.g., via podman run or docker compose) and runs e2e tests against it in the same job. The app exposes coverage via HTTP. Use coverport's --url flag instead of Kubernetes discovery.

Coverage port: Instrumentation servers use 53700 by default (Go, Python, Rust). Map -p 53700:53700 when running locally (or match COVERAGE_PORT if your image sets it). The CLI without --port tries 53700 then 9095 as fallback for legacy setups.

--url base path: Pass the full /coverage endpoint URL (e.g. http://localhost:53700/coverage). The CLI appends ?name=<test-name> — it does not add /coverage for you. A bare http://localhost:53700 returns 404 on Python servers.

Pattern B (Go): Local HTTP collection
- name: Start instrumented application
  run: |
    podman run -d --name app-under-test \
      -p 8080:8080 -p 53700:53700 \
      ${{ env.INSTRUMENTED_IMAGE }}

- name: Run e2e tests
  run: |
    # Run your e2e test suite against http://localhost:8080
    <your-e2e-test-command>

- name: Collect and upload e2e coverage
  if: always()
  run: |
    mkdir -p coverage-output && chmod 777 coverage-output

    # Step 1: Collect coverage from the local HTTP endpoint
    podman run --rm \
      --network host \
      -v $PWD/coverage-output:/workspace/coverage-output \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      collect \
        --url http://localhost:53700/coverage \
        --test-name="e2e-tests" \
        --output=/workspace/coverage-output

    # Step 2: Process and upload to Codecov
    podman run --rm \
      -v $PWD/coverage-output:/workspace/coverage-output:ro \
      -e CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      process \
        --coverage-dir=/workspace/coverage-output \
        --repo-url=${{ github.server_url }}/${{ github.repository }} \
        --commit-sha=${{ github.sha }} \
        --codecov-flags=e2e-tests

- name: Stop application
  if: always()
  run: podman stop app-under-test || true

Key points for Go --url collection:

  • --network host is required so coverport can reach localhost:53700
  • --url must include /coverage (e.g. http://localhost:53700/coverage)
  • Coverport detects format from the /coverage response body (not /health)
  • When using --url (no container image), you must pass --repo-url and --commit-sha to the process command explicitly
  • Legacy Go images may listen on 9095 — use --url http://localhost:9095/coverage and map that port
  • The coverport CLI uses repo URL/commit to clone the repo and remap coverage paths from container paths to source paths
Pattern B (Python): Local HTTP collection

Prefer Pattern A (Kind/K8s) for Python container apps in GitHub Actions. K8s collect checks /health, triggers /coverage/save when needed, fetches /coverage, and generates coverage.xml inside the pod automatically. Pattern B (--url) only saves serialized CoverageData.dumps() bytes as .coverage — the coverport-cli image has no Python, so XML must be generated on the GHA runner after collect using the conversion script below.

Use after completing Steps 3-5 (Python). Only when the app runs via podman run in the same job (not deployed to Kind).

- name: Start instrumented application
  run: |
    podman run -d --name app-under-test \
      -p 8080:8080 -p 53700:53700 \
      ${{ env.INSTRUMENTED_IMAGE }}

- name: Run e2e tests
  run: |
    <your-e2e-test-command>

- name: Collect e2e coverage
  if: always()
  run: |
    mkdir -p coverage-output && chmod 777 coverage-output
    COVERAGE_PORT=53700
    # --url collect does NOT call /coverage/save (unlike K8s collect)
    curl -sf "http://localhost:${COVERAGE_PORT}/coverage/save" || true
    podman run --rm \
      --network host \
      -v $PWD/coverage-output:/workspace/coverage-output \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      collect \
        --url "http://localhost:${COVERAGE_PORT}/coverage" \
        --test-name="e2e-tests" \
        --output=/workspace/coverage-output

- name: Generate Cobertura XML
  if: always()
  run: |
    pip install coverage
    python3 <<'PY'
    import os
    import coverage

    repo = os.path.abspath(".")
    # Must match container WORKDIR and .coveragerc `source` (default /app/)
    container_prefix = "/app/"
    raw_path = "coverage-output/e2e-tests/.coverage"
    xml_path = "coverage-output/e2e-tests/coverage.xml"
    sqlite_path = "coverage-output/e2e-tests/.coverage.local"

    raw = open(raw_path, "rb").read()
    data = coverage.CoverageData(no_disk=True)
    data.loads(raw)

    remapped = coverage.CoverageData(no_disk=True)
    for fn in data.measured_files():
        local_fn = fn.replace(container_prefix, repo + "/")
        lines = data.lines(fn)
        if lines:
            remapped.add_lines({local_fn: lines})
        arcs = data.arcs(fn)
        if arcs:
            remapped.add_arcs({local_fn: arcs})

    db = coverage.CoverageData(basename=sqlite_path)
    db.update(remapped)
    db.write()

    cov = coverage.Coverage(data_file=sqlite_path)
    cov.load()
    cov.xml_report(outfile=xml_path)
    print(f"Wrote {xml_path}")
    PY

- name: Upload e2e coverage to Codecov
  if: always()
  uses: codecov/codecov-action@v6
  with:
    use_oidc: true
    flags: e2e-tests
    files: coverage-output/e2e-tests/coverage.xml
    fail_ci_if_error: false

- name: Stop application
  if: always()
  run: podman stop app-under-test || true

Key points for Python --url collection:

  • --network host is required so coverport can reach localhost
  • --url must be http://localhost:<port>/coverage (CLI appends ?name=; bare host:port → 404)
  • Map the same port in podman run (-p) and COVERAGE_PORT if your image overrides the default
  • collect --url saves coverage-output/<test-name>/.coverage only — serialized CoverageData.dumps() bytes, not SQLite
  • Unlike K8s collect, --url does not call /coverage/save — run curl .../coverage/save first if needed
  • Generate XML on the GHA runner with the Python conversion script above (not coverage xml --data-file= on the raw file)
  • [paths] in .coveragerc alone does not fix host-side XML — set container_prefix in the conversion script to match WORKDIR (default /app/)
  • Do not use coverport process --format=python on --url output until the CLI handles serialized data
  • Smoke-test before collect: curl http://localhost:<port>/health (expect coverage_enabled: true)
Pattern C: Client-Side / Test Runner-Based Coverage Collection

Use when coverage is collected by the test runner rather than from an HTTP endpoint — typically for frontend applications using Cypress or similar tools. No HTTP collection step is needed; instead, mount the coverage output directory into the coverport container for processing.

- name: Build instrumented image
  run: |
    podman build -f Dockerfile.instrumented -t myapp:instrumented .

- name: Run e2e tests with coverage
  run: |
    cd e2e-tests
    npm ci
    npm run cy:run:coverage

- name: Upload e2e coverage to Codecov
  if: always()
  run: |
    mkdir -p coverport-output

    podman run --rm \
      -v $PWD/e2e-tests/.nyc_output:/workspace/coverage:ro \
      -v $PWD/coverport-output:/workspace/output:rw \
      -e CODECOV_TOKEN="${{ secrets.CODECOV_TOKEN }}" \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      process \
        --coverage-dir=/workspace/coverage \
        --format=nyc \
        --repo-url="${{ github.server_url }}/${{ github.repository }}" \
        --commit-sha="${{ github.sha }}" \
        --workspace=/workspace/output \
        --codecov-flags=e2e

Notes for client-side coverage:

  • Use --format=nyc for Istanbul/NYC coverage data (Cypress, Jest)
  • Coverage files are from the test runner output directory, not HTTP
  • No collect step is needed — go straight to process
Pattern D (Rust): App Running Locally via Podman/Docker

Rust coverage requires an additional binary extraction step because llvm-cov needs the original instrumented binary to produce LCOV output from the collected .profraw data.

- name: Set up Rust toolchain
  uses: dtolnay/rust-toolchain@stable
  with:
    components: llvm-tools-preview

- name: Add llvm-tools to PATH
  run: |
    TOOLCHAIN_LIB=$(rustc --print sysroot)/lib
    echo "$(find $TOOLCHAIN_LIB -name 'llvm-profdata' -exec dirname {} \;)" >> $GITHUB_PATH

- name: Build instrumented image
  run: |
    podman build --build-arg ENABLE_COVERAGE=true -t myapp:instrumented .

- name: Extract binary from image
  run: |
    CONTAINER_ID=$(podman create myapp:instrumented)
    podman cp $CONTAINER_ID:/app/<binary-name> ./coverage-binary
    podman rm $CONTAINER_ID
    chmod +x ./coverage-binary

- name: Start instrumented application
  run: |
    podman run -d --name app-under-test \
      -p 8080:8080 -p 53700:53700 \
      myapp:instrumented

- name: Run e2e tests
  run: |
    <your-e2e-test-command>

- name: Collect and upload Rust e2e coverage
  if: always()
  run: |
    mkdir -p coverage-output && chmod 777 coverage-output

    # Step 1: Collect profraw from the coverage HTTP endpoint
    podman run --rm \
      --network host \
      -v $PWD/coverage-output:/workspace/coverage-output \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      collect \
        --url http://localhost:53700/coverage \
        --test-name="e2e-tests" \
        --output=/workspace/coverage-output

    # Step 2: Process profraw → LCOV and upload to Codecov
    podman run --rm \
      -v $PWD/coverage-output:/workspace/coverage-output \
      -v $PWD/coverage-binary:/workspace/binary:ro \
      -e CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} \
      -e COVERAGE_BINARY=/workspace/binary \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      process \
        --coverage-dir=/workspace/coverage-output \
        --format=rust \
        --repo-url=${{ github.server_url }}/${{ github.repository }} \
        --commit-sha=${{ github.sha }} \
        --codecov-flags=e2e-tests

- name: Stop application
  if: always()
  run: podman stop app-under-test || true

Key points for Rust coverage in GitHub Actions:

  • Port 53700 (not 9095) — Rust uses the coverport standard port
  • --format=rust tells coverport to use llvm-profdata + llvm-cov for processing
  • COVERAGE_BINARY environment variable (or --binary flag) is required — points to the extracted instrumented binary
  • The binary must match the exact same build that produced the container image
  • llvm-tools-preview must be installed so llvm-profdata and llvm-cov are available
  • Binary extraction via podman cp avoids needing to share volumes or rebuild locally
Self-Hosted Codecov with Coverport

When uploading to a self-hosted Codecov instance (see codecov-config/CONFIG.md), add --codecov-url to the process command:

    podman run --rm \
      -e CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} \
      quay.io/konflux-ci/konflux-devprod/coverport-cli:${COVERPORT_TAG} \
      process \
        --codecov-url=<CODECOV_INSTANCE_URL> \
        --coverage-dir=/workspace/coverage-output \
        --repo-url=${{ github.server_url }}/${{ github.repository }} \
        --commit-sha=${{ github.sha }} \
        --codecov-flags=e2e-tests

OIDC is not available for coverport uploads — coverport uses the Codecov CLI internally, which always requires a CODECOV_TOKEN. The codecov/codecov-action with OIDC is only for unit test uploads (see Step 7a).

Step 8: Document Manual Steps

After making all changes, inform the user they need to create a Kubernetes secret.

IMPORTANT: The secret must be created in the namespace where your build and integration pipelines run. This is typically your tenant namespace (e.g., my-tenant, not a specific repository namespace like rhtap-release-2-tenant). You can identify the correct namespace by checking the namespace field in your .tekton/*-push.yaml file.

Option A - Using kubectl:

# First, create the dockerconfig JSON file
cat > /tmp/dockerconfig.json <<EOF
{"auths":{"quay.io":{"auth":"<base64-encoded-quay-user:token>","email":""}}}
EOF

# Create the secret with both keys in YOUR tenant namespace
kubectl create secret generic coverport-secrets \
  --from-literal=codecov-token=<your-codecov-token> \
  --from-file=oci-storage-dockerconfigjson=/tmp/dockerconfig.json \
  -n <your-tenant-namespace>

# Clean up
rm /tmp/dockerconfig.json

Option B - Using YAML:

apiVersion: v1
kind: Secret
metadata:
  name: coverport-secrets
  namespace: <your-tenant-namespace>  # Replace with your tenant namespace
type: Opaque
stringData:
  codecov-token: <your-codecov-token>
  oci-storage-dockerconfigjson: '{"auths":{"quay.io":{"auth":"<base64-encoded-quay-user:token>","email":""}}}'

Required secret keys:

  • codecov-token - Your Codecov API token for uploading coverage reports to Codecov
  • oci-storage-dockerconfigjson - Docker config JSON with Quay.io credentials for pushing coverage test artifacts to an OCI container registry
    • This is used by the collect-and-upload-coverage task to store coverage data as OCI artifacts in quay.io
    • The coverage collection process extracts coverage data from instrumented containers and pushes it to the OCI registry before uploading to Codecov
    • The auth value should be base64-encoded username:token
    • To encode: echo -n "quay-username:quay-token" | base64
    • You need push access to the quay.io repository specified in the e2e pipeline's oci-container-repo parameter

Step 9: Post-Integration Validation Checklist

Before committing the changes, verify all modifications are correct:

Local validation (already completed in Step 5.5):

  • podman build (production) succeeds
  • Go/Rust: podman build --build-arg ENABLE_COVERAGE=true (instrumented) succeeds
  • Python: podman build --target test (instrumented) succeeds
  • Go/Rust instrumented build logs show "Building with coverage instrumentation..."
  • Python instrumented container logs show [coverage-wrapper] HTTP server listening on port <port>
  • Go production build logs show "Building production binary..."
  • Python production stage runs plain Gunicorn (no coverage_server.py wrapper)

Go module setup checklist:

  • go.mod has coverport dependency added (as direct, not indirect)
  • go.sum has coverport checksums
  • coverage_init.go exists at the root of the module with correct build tags
  • go mod tidy produces no diff (dependency is correctly classified)

Python container instrumentation checklist:

  • Four instrumentation files vendored (coverage_server.py, sitecustomize.py, .coveragerc, gunicorn_coverage.py)
  • sitecustomize.py installed into site-packages in the instrumented Dockerfile stage
  • COVERAGE_PROCESS_START, COVERAGE_DATA_DIR=/dev/shm, and TMPDIR=/dev/shm set in instrumented image
  • CMD uses coverage_server.py wrapper with Gunicorn and gunicorn_coverage.py config
  • .coveragerc source path matches container WORKDIR
  • Coverage HTTP port exposed and mapped (default 53700, or COVERAGE_PORT if set)
  • Pattern A (Kind): upload coverage-output/<test-name>/coverage.xml after collect
  • Pattern B (--url): --url uses http://localhost:<port>/coverage; host-side deserialize + XML step after collect
  • Pattern B (--url): curl .../coverage/save before collect when workers have not flushed data
  • curl http://localhost:<port>/health returns coverage_enabled: true (local validation)

File modifications checklist (Tekton path):

  • Dockerfile has ENABLE_COVERAGE build arg (removed COVERAGE_SERVER_URL)
  • Dockerfile has conditional build logic with -tags=coverage flag
  • .tekton/*-push.yaml has build-instrumented-image task after prefetch-dependencies
  • .tekton/*-push.yaml instrumented task uses buildah-oci-ta (not buildah-remote-oci-ta)
  • .tekton/*-push.yaml instrumented task uses HERMETIC: $(params.hermetic) and PREFETCH_INPUT: $(params.prefetch-input)
  • .tekton/*-pull-request.yaml has hermetic: "true" and prefetch-input enabled
  • .tekton/*-pull-request.yaml has ENABLE_COVERAGE=true in BUILD_ARGS
  • integration-tests/pipelines/*e2e*.yaml uses test-metadata v0.4
  • integration-tests/pipelines/*e2e*.yaml has collect-and-upload-coverage task
  • integration-tests/pipelines/*e2e*.yaml updated image references (if applicable)

File modifications checklist (GitHub Actions path):

  • .github/workflows/pr.y*ml has flags: unit-tests in codecov action
  • .github/workflows/codecov.y*ml has flags: unit-tests in codecov action
  • E2e workflow has coverport collect/process steps using podman run
  • Correct coverport collection pattern chosen (Kubernetes, local --url, or client-side)
  • CODECOV_TOKEN secret is configured in GitHub repository settings
  • For --url pattern: --network host is set on the podman commands
  • For --url / client-side patterns: --repo-url and --commit-sha are passed to process
  • For self-hosted Codecov: --codecov-url is passed to process
  • For Kubernetes pattern: kubeconfig copied with chmod 644 before mounting
  • For all patterns: output directory created with chmod 777
  • If e2e test suite rebuilds the image (e.g., kubebuilder BeforeSuite): ENABLE_COVERAGE is passed through

File modifications checklist (GitHub Actions-only path — no Tekton e2e pipeline):

  • Tekton push pipelines are NOT modified (no build-instrumented-image task)
  • Tekton PR pipelines are NOT modified (no ENABLE_COVERAGE=true in BUILD_ARGS)
  • Makefile or build scripts pass ENABLE_COVERAGE to container build commands
  • GitHub Actions e2e workflow sets ENABLE_COVERAGE=true when building images
  • Coverport collect step runs after e2e tests with if: always()

Documentation provided to user:

  • Instructions for creating coverport-secrets Kubernetes secret in their tenant namespace (Tekton path)
  • Explanation that the namespace should be where their build and integration pipelines run
  • Required secret keys: codecov-token and oci-storage-dockerconfigjson
  • Explanation of what oci-storage-dockerconfigjson is used for (pushing coverage artifacts to quay.io)
  • Instructions for encoding auth credentials
  • Note about needing push access to the quay.io repository
  • For GitHub Actions: CODECOV_TOKEN must be added as a repository secret

Summary to provide user (Tekton + GitHub Actions): List all modified files with brief description of changes:

Modified files:
- coverage_init.go: NEW - Coverage initialization with build tags
- go.mod: Added coverport dependency
- go.sum: Added coverport checksums
- Dockerfile: Added coverage instrumentation with build tags
- .tekton/<name>-push.yaml: Added instrumented image build task with hermetic support
- .tekton/<name>-pull-request.yaml: Enabled coverage and hermetic builds for PR builds
- integration-tests/pipelines/<name>-e2e-pipeline.yaml: Added coverage collection
- .github/workflows/pr.yml: Added unit-tests flag
- .github/workflows/codecov.yml: Added unit-tests flag
- .github/workflows/e2e.yml: Added coverport collect/process steps for e2e coverage

Validation

After integration is deployed to CI/CD, provide these verification steps to the user:

  1. Check instrumented image build:

    • Push a commit to main branch
    • Verify the push pipeline creates an image with .instrumented tag
    • Check build logs for "Building with coverage instrumentation..." message
  2. Check e2e coverage collection (Tekton path):

    • Run e2e tests via integration pipeline
    • Verify collect-and-upload-coverage task executes successfully
    • Check Codecov dashboard for coverage data with e2e-tests flag
  3. Check e2e coverage collection (GitHub Actions path):

    • Trigger the e2e workflow
    • Verify the coverport collect and process steps succeed in the logs
    • Check Codecov dashboard for coverage data with e2e-tests flag
    • For --url collection: verify the app container was reachable on the coverage port and --url included /coverage (e.g. http://localhost:53700/coverage) (9095 only for legacy Go instrumentation)
  4. Check unit test coverage:

    • Create a PR
    • Verify unit tests upload coverage with unit-tests flag
    • Check Codecov shows both unit and e2e coverage

Troubleshooting

Common issues and solutions:

Build error: "coverage_init.go not found" or "package not imported"

  • Cause: The coverage_init.go file is missing or in the wrong location
  • Solution:
    • Ensure coverage_init.go exists at the root of your Go module (same directory as main.go)
    • Verify the file has the correct //go:build coverage build tag
    • Check that the package declaration matches your main package (package main)

Build error: "cannot find package"

  • Cause: Coverport dependency not properly added to Go modules
  • Solution:
    • Run go get github.com/konflux-ci/coverport/instrumentation/go
    • Verify go.mod has the coverport dependency
    • Run go mod tidy to clean up dependencies

CI check fails: "Go mod state is not clean"

  • Cause: go get adds dependencies as // indirect, but go mod tidy reclassifies direct imports
  • Solution: Always run go mod tidy after go get. The coverport import in coverage_init.go (even behind a build tag) makes it a direct dependency. If CI runs go mod tidy and checks for diffs, the // indirect annotation will cause a failure.

Instrumented build fails:

  • Verify coverport Go module dependency is in go.mod and go.sum
  • Check that coverage_init.go has the correct build tag syntax (//go:build coverage, not // +build coverage)
  • Ensure the Dockerfile build command includes -tags=coverage
  • Verify hermetic mode is enabled with proper prefetch configuration
  • Ensure you're using buildah-oci-ta for instrumented builds in push pipeline, not buildah-remote-oci-ta
  • Verify there's no matrix build or PLATFORM parameter for the instrumented image task

Hermetic build fails with "cannot download dependencies"

  • Cause: Go module dependencies not properly prefetched
  • Solution:
    • Ensure hermetic: "true" is set in the pipeline parameters
    • Verify prefetch-input is set correctly: {"type": "gomod", "path": "."}
    • Check that the prefetch-dependencies task completed successfully
    • Review prefetch task logs for any download errors

Coverage data not uploaded:

  • Verify coverport-secrets exists in your tenant namespace (the namespace where your build and integration pipelines run)
  • Check codecov-token key exists in the secret
  • Check oci-storage-dockerconfigjson key exists and is valid (should be a valid Docker config JSON)
  • Verify you h

Truncated - read the full file at https://github.com/konflux-ci/coverport/blob/c914ae07e9f0b423ca73d06ed613f78bd177e240/.claude/skills/coverport-integration/SKILL.md.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/konflux-ci-coverport-coverport-integration/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

konflux-ci-coverport-coverport-integration.ocm.jsonjson
{
  "ocm": "1",
  "id": "konflux-ci-coverport-coverport-integration",
  "kind": "skill",
  "name": "coverport-integration",
  "description": "Integrate coverport into repositories to enable e2e test coverage collection and upload to Codecov. Supports Go, Python, Node.js, and Rust applications with Tekton/Konflux pipelines and GitHub Actions (using coverport CLI container via podman). Use this skill when users ask to integrate coverport, add e2e coverage tracking, or set up coverage instrumentation.",
  "publisher": "konflux-ci",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "coding"
    ],
    "tags": [
      "skill-md",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Integrate coverport into repositories to enable e2e test coverage collection and upload to Codecov. Supports Go, Python, Node.js, and Rust applications with Tekton/Konflux pipelines and GitHub Actions (using coverport CLI container via podman). Use this skill when users ask to integrate coverport, add e2e coverage tracking, or set up coverage instrumentation."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/konflux-ci/coverport",
      "path": ".claude/skills/coverport-integration/SKILL.md",
      "ref": "c914ae07e9f0b423ca73d06ed613f78bd177e240",
      "url": "https://github.com/konflux-ci/coverport/blob/c914ae07e9f0b423ca73d06ed613f78bd177e240/.claude/skills/coverport-integration/SKILL.md",
      "key": "konflux-ci/coverport/.claude/skills/coverport-integration/SKILL.md"
    }
  },
  "instructions": "# Coverport Integration Skill\n\nThis skill automates the integration of coverport into repositories for e2e test coverage collection and upload to Codecov. It supports both Tekton/Konflux pipelines and GitHub Actions workflows.\n\n## What is Coverport?\n\nCoverport is a tool that enables e2e test coverage collection by:\n1. Building instrumented container images (Go with `-cover`, Python with coverage wrapper, Node.js with V8 inspector, Rust with `-C instrument-coverage`)\n2. Collecting coverage data from running containers during e2e tests — via HTTP endpoint or from test runner output\n3. Processing",
  "cost": {
    "context_tokens": 22330
  }
}

Fetch it by URL: GET /api/v1/registry/konflux-ci-coverport-coverport-integration/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.