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
72 changes: 72 additions & 0 deletions .github/scripts/check-otel-k6-compat.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
#
# Verify that this module does not require a newer OpenTelemetry version than the
# latest tagged k6 release.
#
# Why: zitadel/xk6-modules imports both this library and k6, and k6 pins the whole
# OpenTelemetry family (API, SDK and exporters) to a single version. If our go.mod
# asks for a higher otel version, minimal version selection raises only the API
# modules there while the SDK and exporters stay on k6's version. That mixed set has
# broken the load test build before. See CONTRIBUTING.md.
#
# Reads only public data and needs no credentials.
#
# Usage:
# .github/scripts/check-otel-k6-compat.sh
#
# Environment:
# K6_VERSION check against this k6 tag instead of resolving the latest release
#
set -euo pipefail

XK6_GOMOD_URL="https://raw.githubusercontent.com/zitadel/xk6-modules/main/go.mod"
K6_REPO_URL="https://github.com/grafana/k6"
K6_GOMOD_URL="https://raw.githubusercontent.com/grafana/k6/%s/go.mod"

# Stable otel modules only: the whole go.opentelemetry.io/otel tree shares one version,
# but go.opentelemetry.io/auto/sdk and go.opentelemetry.io/proto/otlp are versioned
# independently, and the signal packages that are still v0 (log, ...) are not lock-stepped
# with the v1 ones either.
otel_versions() {
grep -oE 'go\.opentelemetry\.io/otel(/[a-z0-9/]+)? v1\.[0-9]+\.[0-9]+' \
| awk '{print $2}' | sort -Vu
}

ours=$(otel_versions < go.mod | tail -1)
[ -n "$ours" ] || { echo "go.mod requires no stable go.opentelemetry.io/otel module, nothing to check"; exit 0; }

# The k6 major line the load tests build against, e.g. "go.k6.io/k6/v2" -> "v2".
k6_major=$(curl --retry 3 --retry-delay 2 --retry-connrefused --connect-timeout 10 --max-time 30 -sSfL "$XK6_GOMOD_URL" | grep -oE 'go\.k6\.io/k6(/v[0-9]+)?' | head -1 | grep -oE 'v[0-9]+$' || true)
k6_major=${k6_major:-v1}

if [ -n "${K6_VERSION:-}" ]; then
k6_tag="$K6_VERSION"
else
# Release tags straight from the remote: no API, so no rate limit and no token.
# Draft releases do not push a tag, and the pattern drops prereleases (v2.0.0-rc1).
k6_tag=$(git ls-remote --tags --refs "$K6_REPO_URL" "${k6_major}.*" \
| awk -F/ '{print $NF}' \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
| sort -V | tail -1)
[ -n "$k6_tag" ] || { echo "::error::could not resolve the latest k6 ${k6_major} release"; exit 1; }
fi

# The lowest otel version k6 pins is the ceiling: anything above it is a version
# the k6 build will not have.
theirs=$(curl -sSfL "$(printf "$K6_GOMOD_URL" "$k6_tag")" | otel_versions | head -1)
[ -n "$theirs" ] || { echo "::error::no stable otel requirement found in k6 ${k6_tag} go.mod"; exit 1; }

echo "this module requires otel ${ours}"
echo "k6 ${k6_tag} pins otel ${theirs}"

if [ "$(printf '%s\n%s\n' "$ours" "$theirs" | sort -V | tail -1)" != "$theirs" ]; then
cat <<EOF
::error::otel ${ours} is newer than the ${theirs} version pinned by k6 ${k6_tag}
This would break the zitadel/xk6-modules build used for the ZITADEL load tests.
Hold this bump until k6 ships a release on otel ${ours} or newer.
See the "Dependency updates" section in CONTRIBUTING.md.
EOF
exit 1
fi

echo "ok: otel is aligned with k6 ${k6_tag}"
23 changes: 23 additions & 0 deletions .github/workflows/otel-k6-compat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: OpenTelemetry k6 compatibility

# zitadel/xk6-modules imports both this library and k6, and k6 pins the whole
# OpenTelemetry family to a single version. Requiring a newer otel here silently
# breaks that build, which our own test matrix cannot detect.
# See the "Dependency updates" section in CONTRIBUTING.md.
on:
push:
branches: [main, next]
pull_request:
branches: ['**']
workflow_dispatch:

permissions:
contents: read

jobs:
otel-k6-compat:
name: otel not ahead of k6
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- run: .github/scripts/check-otel-k6-compat.sh
55 changes: 55 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,61 @@ When in doubt, omit the scope — `<type>: <short summary>` is always valid.

Provide a brief description of the change.

## Dependency updates

Most dependency bumps (Dependabot or manual) can be merged once CI is green.
There is one exception that CI here cannot catch: **OpenTelemetry**.

### OpenTelemetry must not get ahead of k6

[`zitadel/xk6-modules`](https://github.com/zitadel/xk6-modules) — the k6 extension used for
ZITADEL's load tests — imports both this library and [k6](https://github.com/grafana/k6).
k6 pins the whole OpenTelemetry family (API, SDK and exporters) to a single version.
Go's minimal version selection means an `otel` requirement in *our* `go.mod` that is higher
than k6's raises only the API modules, while k6 keeps the SDK and exporters at its own
version. That mixed set has broken the xk6 build in the past.

So the rule is: **never require a `go.opentelemetry.io/otel*` version higher than the one in
the latest tagged k6 release.** Being on the same version, or lower, is fine.

### How to verify

CI does this for you: the **otel not ahead of k6** check
([`otel-k6-compat.yml`](.github/workflows/otel-k6-compat.yml)) runs on every PR and fails
if this module requires a newer otel than the latest k6 release. To run the same check
locally:

```bash
.github/scripts/check-otel-k6-compat.sh
```

It resolves the latest tagged k6 release on the major line `xk6-modules` builds against,
reads the otel versions from that tag's `go.mod`, and compares them with ours. Set
`K6_VERSION=v2.1.0` to check against a specific k6 tag instead.

By hand, the same thing is: read k6's `go.mod` at a **tagged release** (not `master`, which
is usually ahead) and compare with our [`go.mod`](go.mod):

```bash
tag=$(gh release view --repo grafana/k6 --json tagName -q .tagName)
curl -sL "https://raw.githubusercontent.com/grafana/k6/${tag}/go.mod" | grep opentelemetry
```

If the PR would push us above k6, hold it until k6 catches up.

Alternatively, from a checkout of `xk6-modules`, Grafana's
[`xk6 sync`](https://github.com/grafana/xk6#xk6-sync) command aligns an extension's
dependencies with the k6 version in its `go.mod` and shows the mismatches. A quick manual
check is to bump the otel modules in `xk6-modules` to the proposed version *without*
touching the k6 requirement, and confirm `go build ./...` still passes.

### The `2.12.x` branch

`2.12.x` is the maintenance branch for v2 and still targets an old Go version. Dependabot
regularly proposes bumps there (OpenTelemetry included) that rewrite the `go` directive and
break the build. Those PRs should be closed rather than merged unless the bump is a security
fix that genuinely applies to v2.

## Want to use the library?

Checkout the [examples folder](example) for different client and server implementations.
Expand Down
Loading