From 346231c8d7e7c85d4879e13df375bc713ccebd2b Mon Sep 17 00:00:00 2001 From: Sibasis Padhi Date: Fri, 9 Jan 2026 16:23:26 -0600 Subject: [PATCH 1/4] docs: add dependency management guidelines to CONTRIBUTING.md Fixes #1913 Adds comprehensive guidance on managing dependencies in client_golang, based on learnings from the Slack discussion and recent dependency-related PRs. Covers the four key topics requested: 1. Vendoring vs. direct dependencies: - Prefer direct dependencies with Go modules - Vendoring only as last resort for critical cases - Go modules provide reproducible builds via go.sum 2. Using unstable dependencies: - Avoid pre-1.0, alpha/beta, and commit-based versions in production - Exceptions require strong justification and maintainer approval - Includes practical examples of good vs. bad dependency versions 3. Indirect dependencies: - KEY INSIGHT: Indirect deps don't always propagate to users! - Go modules only propagate deps that are actually used in import chain - Example: common's kingpin dependency doesn't leak to client_golang users - Practical commands: 'go mod why' and 'go mod graph' for analysis 4. Dependencies in different contexts: - Production code: Strictest (affects all users, requires approval) - Test code: Flexible (test deps don't leak to importers) - Examples: Most flexible (but imports CAN cause dep leakage) - Real example: Removing example_test.go imports cleaned up many deps The guidelines emphasize that client_golang is part of the Kubernetes dependency chain, making our dependency decisions impact thousands of projects. Includes the context that adding dependencies creates significant work for Kubernetes maintainers. Also covers: - Why we're strict (Kubernetes factor, transitive dep problem) - Dependency evaluation criteria - Update procedures - Red flags for rejecting dependencies - Practical examples and real cases from our history Signed-off-by: Sibasis Padhi --- CONTRIBUTING.md | 243 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abc101b18..a7c61b0fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,3 +20,246 @@ Prometheus uses GitHub to manage reviews of pull requests. Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). * Be sure to sign off on the [DCO](https://github.com/probot/dco#how-it-works) + +## Managing Dependencies + +`client_golang` is a critical library in the Prometheus ecosystem, used by thousands of projects +including Kubernetes. Any dependency we add becomes a transitive dependency for all our users, +which is why we must be extremely careful when adding or updating dependencies. + +### Why We're Strict About Dependencies + +**The Kubernetes Factor:** +Kubernetes depends on `client_golang`, and adding dependencies creates significant work for the +Kubernetes maintainers and affects the entire Kubernetes ecosystem. This dependency chain means +our decisions impact far more projects than just our direct users. + +**The Transitive Dependency Problem:** +Every dependency we add potentially becomes a dependency for thousands of projects, bringing with it: +- Increased attack surface for security vulnerabilities in our dependency chain +- Potential version conflicts with users' other dependencies + +### Understanding Indirect Dependencies + +**Key Insight:** Not all indirect dependencies propagate to our users! + +Go modules are smarter than you might think. An indirect dependency in our `go.mod` doesn't +automatically become an indirect dependency for projects that import `client_golang`. + +**How it works:** +- If we depend on package A, and A depends on package B, but we never import/use anything from B, + then B will **not** propagate to projects that import `client_golang` +- Go modules only propagate dependencies that are actually used in the import chain + +**Example from our codebase:** +- `prometheus/common` has many dependencies (e.g., `kingpin` for CLI parsing) +- `client_golang` depends on `common` +- But `client_golang` users don't get `kingpin` as an indirect dependency because `client_golang` + doesn't import the parts of `common` that use `kingpin` + +**Testing this:** +```bash +# See why a package is in our dependencies +go mod why github.com/some/package + +# See the full dependency graph +go mod graph + +# Test if a dependency propagates by creating a test module that imports client_golang +# and checking if the dependency appears in its go.mod +``` + +**Important exception - Examples can leak dependencies:** +Code in `examples/` or example test files (like `example_test.go`) that imports packages will +cause those dependencies to appear in `go.mod` and potentially leak to users. Testing during +recent dependency discussions showed that example code imports (such as `api/prometheus/v1/example_test.go` +importing `prometheus/common/config`) can cause many indirect dependencies to leak. + +### Adding New Dependencies + +**Key Concerns:** + +When evaluating new dependencies, critical factors include: + +- **Security:** Vulnerabilities in our dependency chain affect all users +- **Transitive dependencies:** How many indirect dependencies does it bring? +- **Version conflicts:** Can impact downstream projects (like KEDA, Kubernetes, and other users) +- **Licensing:** Must be Apache 2.0 compatible (avoid GPL, LGPL, or copyleft licenses) + +**Process for production code dependencies:** + +1. **Open an issue first** to discuss with maintainers +2. **Provide justification:** + - What problem does it solve? + - Why can't we implement it ourselves? + - What alternatives were considered? +3. **Wait for maintainer approval** before implementing +4. **Be prepared for alternatives** if concerns arise during review + +**General evaluation criteria to consider:** + +- Is the dependency actively maintained? +- Does it have a stable release (prefer v1.0.0+)? +- What is the security track record? +- How complex is its transitive dependency tree? +- Will it benefit the majority of users? + +### Vendoring vs. Direct Dependencies + +**Prefer direct dependencies** in almost all cases. +Go modules handle version management well, and direct dependencies make it easier to track +updates and security patches. + +**Consider vendoring** (via `go mod vendor`) only when: +- We need absolute version stability for a critical dependency +- Upstream is unmaintained but we need the stable code +- There's a specific technical reason (discuss with maintainers first) + +**Note:** With Go modules, vendoring is rarely necessary. The `go.sum` file provides +reproducible builds without vendoring. + +### Using Unstable Dependencies + +**For production code, strongly prefer stable dependencies:** + +- Prefer semantic versioned releases (v1.0.0+) when available +- Prefer tagged versions over commit hashes +- Avoid alpha/beta releases when stable alternatives exist + +**Pragmatic reality:** + +We do use some pre-1.0 dependencies (like `prometheus/client_model v0.6.2`) and have +indirect dependencies with commit-based versions where necessary. The key is: +- New direct dependencies should use stable versions when possible +- Pre-1.0 or commit-based versions require justification +- Discuss with maintainers if unsure + +**Example of dependency versioning preferences:** +```go +// Most preferred: Stable semantic version +require github.com/example/lib v1.2.3 + +// Acceptable if no stable alternative: Pre-1.0 version +require github.com/example/lib v0.6.2 + +// Least preferred: Commit-based (sometimes unavoidable for indirect deps) +require github.com/example/lib v0.0.0-20230101120000-abcdef123456 +``` + +### Dependencies in Different Contexts + +**Production code** (`prometheus/`, `prometheus/...`): +- **Strictest requirements** - affects all users +- Prefer stable versions (v1.0.0+) when available +- Requires maintainer approval +- Minimal transitive dependencies preferred +- Every dependency must be justified + +**Test code** (`*_test.go`): +- **More flexible** - test dependencies don't propagate to users! +- Go modules improved in recent versions: test deps stay in our `go.mod` but don't leak to importers +- Can use testing libraries (`go.uber.org/goleak`, `github.com/google/go-cmp`, etc.) +- Still prefer stable, well-maintained tools + +**Examples** (`examples/` directory): +- **Most flexible** - example code isn't imported by users +- Can use newer versions to demonstrate features +- Still prefer stable dependencies when possible +- **Warning:** Example imports CAN cause indirect deps to appear in `go.mod` + +**Real example discovered in dependency analysis:** + +The file `api/prometheus/v1/example_test.go` imports `prometheus/common/config`. Testing +showed that this single import causes many transitive dependencies to appear in our `go.mod`. +Removing such imports would clean up numerous indirect dependencies because those packages +aren't actually used in the production code path. + +### Dependency Update Process + +When updating dependencies, use standard Go module workflows: + +**Checking and updating:** +```bash +# Check for available updates +go list -u -m all + +# Update a specific dependency +go get -u github.com/prometheus/client_model@v0.6.2 + +# Clean up and verify +go mod tidy +go mod verify +``` + +**Understanding dependency usage:** +```bash +# See why a package is in our dependencies +go mod why github.com/some/package + +# See the full dependency graph +go mod graph +``` + +**Testing:** +```bash +make test +``` + +**Before committing:** +- Run the full test suite +- Review the dependency's CHANGELOG for breaking changes +- Verify `go.sum` changes are reasonable + +**Consider the impact:** +- Will the update affect downstream projects (like Kubernetes)? +- Does it introduce new transitive dependencies? +- Are there any security advisories for the old or new version? + +### Red Flags: Dependencies to Reject + +When evaluating dependencies, be cautious of: + +- Known unpatched security vulnerabilities +- Incompatible licenses (must be Apache 2.0 compatible) +- Excessive transitive dependencies +- Unmaintained projects (no recent activity or releases) +- Poor maintenance indicators (unresponsive maintainers, accumulating issues) + +Discuss with maintainers if a dependency raises concerns. + +### Example Dependency Evaluation + +When proposing a new dependency in an issue, provide analysis covering: + +**Basic information:** +- Purpose and which component needs it +- Version and maintenance status +- License compatibility + +**Impact analysis:** +- Will it propagate to users? (consider which code imports it) +- How many transitive dependencies does it bring? +- Any security concerns? + +**Alternatives:** +- What alternatives were considered? +- Why are they not suitable? + +**Recommendation:** +- Your assessment with justification + +Maintainers will review and provide feedback before you proceed with implementation. + +### Resources + +- [Go Modules Reference](https://go.dev/ref/mod) - comprehensive but dense +- [Semantic Versioning](https://semver.org/) +- [Go Security Best Practices](https://go.dev/security/best-practices) + +### Key Takeaways + +1. **Indirect dependencies don't always propagate** - Go modules are smart about this +2. **Test dependencies don't leak** to projects that import us +3. **Example code can leak dependencies** - be careful with example imports +4. **We're part of the Kubernetes dependency chain** - our choices have wide impact +5. **When in doubt, discuss first** - open an issue before adding dependencies From fe80c5bf29b1605bf7f28c52e0f527bcb7ff33ef Mon Sep 17 00:00:00 2001 From: Sibasis Padhi Date: Mon, 12 Jan 2026 08:37:27 -0600 Subject: [PATCH 2/4] docs: add missing period for consistency Address feedback from @bwplotka to add proper punctuation. Signed-off-by: Sibasis Padhi --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7c61b0fe..1147064d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,7 +48,7 @@ automatically become an indirect dependency for projects that import `client_gol **How it works:** - If we depend on package A, and A depends on package B, but we never import/use anything from B, - then B will **not** propagate to projects that import `client_golang` + then B will **not** propagate to projects that import `client_golang`. - Go modules only propagate dependencies that are actually used in the import chain **Example from our codebase:** From f4db1ac4f2851919311e8b927db07eea615ac5fb Mon Sep 17 00:00:00 2001 From: Sibasis Padhi Date: Mon, 12 Jan 2026 08:39:36 -0600 Subject: [PATCH 3/4] docs: remove redundant bullet point The second bullet point was redundant with the first - both explained that Go modules only propagate dependencies that are actually used. Addresses feedback from @bwplotka. Signed-off-by: Sibasis Padhi --- CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1147064d9..9137bbe49 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,6 @@ automatically become an indirect dependency for projects that import `client_gol **How it works:** - If we depend on package A, and A depends on package B, but we never import/use anything from B, then B will **not** propagate to projects that import `client_golang`. -- Go modules only propagate dependencies that are actually used in the import chain **Example from our codebase:** - `prometheus/common` has many dependencies (e.g., `kingpin` for CLI parsing) From a9bc630dca54d93514278d0170808f13d8f29b76 Mon Sep 17 00:00:00 2001 From: Sibasis Padhi Date: Mon, 12 Jan 2026 09:30:49 -0600 Subject: [PATCH 4/4] docs: address @bwplotka feedback on CONTRIBUTING.md - Add missing trailing periods for consistency - Remove redundant bullet point about dependency propagation - Add permanent links and specific package examples (kingpin/promslog/flag) - Improve 'Testing this' section with concrete, actionable examples - Remove confusing 'exception' section about example files - Remove verbose sections (Unstable Dependencies, Update Process, Red Flags) - Remove AI-watery language and blog-post style sections - Reduce file from ~265 to 138 lines (~50% reduction) Addresses feedback from prometheus/client_golang#1938 Signed-off-by: Sibasis Padhi --- CONTRIBUTING.md | 186 ++++++++---------------------------------------- 1 file changed, 30 insertions(+), 156 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9137bbe49..9700d31b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,28 +51,42 @@ automatically become an indirect dependency for projects that import `client_gol then B will **not** propagate to projects that import `client_golang`. **Example from our codebase:** -- `prometheus/common` has many dependencies (e.g., `kingpin` for CLI parsing) -- `client_golang` depends on `common` +- [`prometheus/common`](https://github.com/prometheus/common) has many dependencies, including + [`kingpin`](https://github.com/alecthomas/kingpin) for CLI parsing (used only in + [`promslog/flag`](https://github.com/prometheus/common/tree/main/promslog/flag)). +- [`client_golang`](https://github.com/prometheus/client_golang) depends on `common`. - But `client_golang` users don't get `kingpin` as an indirect dependency because `client_golang` - doesn't import the parts of `common` that use `kingpin` + doesn't import `promslog/flag` anywhere. **Testing this:** -```bash -# See why a package is in our dependencies -go mod why github.com/some/package -# See the full dependency graph -go mod graph +To verify a dependency doesn't propagate to users, create a test module: -# Test if a dependency propagates by creating a test module that imports client_golang -# and checking if the dependency appears in its go.mod +```bash +# 1. Create a test module that imports client_golang +mkdir /tmp/test-client-golang-deps && cd /tmp/test-client-golang-deps +go mod init example.com/test +go get github.com/prometheus/client_golang + +# 2. Check if the dependency appears in your module +go mod why github.com/alecthomas/kingpin +# Expected output: (main module does not need package github.com/alecthomas/kingpin) + +# 3. Verify it's not in go.mod +grep kingpin go.mod +# Expected: no output (kingpin is not propagated) ``` -**Important exception - Examples can leak dependencies:** -Code in `examples/` or example test files (like `example_test.go`) that imports packages will -cause those dependencies to appear in `go.mod` and potentially leak to users. Testing during -recent dependency discussions showed that example code imports (such as `api/prometheus/v1/example_test.go` -importing `prometheus/common/config`) can cause many indirect dependencies to leak. +To check why a dependency IS in `client_golang`'s own go.mod (run from this repository): + +```bash +# See why client_golang needs a package +go mod why github.com/prometheus/common +# Output shows the import chain from client_golang code + +# See full dependency graph +go mod graph | grep common +``` ### Adding New Dependencies @@ -85,7 +99,7 @@ When evaluating new dependencies, critical factors include: - **Version conflicts:** Can impact downstream projects (like KEDA, Kubernetes, and other users) - **Licensing:** Must be Apache 2.0 compatible (avoid GPL, LGPL, or copyleft licenses) -**Process for production code dependencies:** +**Process for adding production code dependencies:** 1. **Open an issue first** to discuss with maintainers 2. **Provide justification:** @@ -117,148 +131,8 @@ updates and security patches. **Note:** With Go modules, vendoring is rarely necessary. The `go.sum` file provides reproducible builds without vendoring. -### Using Unstable Dependencies - -**For production code, strongly prefer stable dependencies:** - -- Prefer semantic versioned releases (v1.0.0+) when available -- Prefer tagged versions over commit hashes -- Avoid alpha/beta releases when stable alternatives exist - -**Pragmatic reality:** - -We do use some pre-1.0 dependencies (like `prometheus/client_model v0.6.2`) and have -indirect dependencies with commit-based versions where necessary. The key is: -- New direct dependencies should use stable versions when possible -- Pre-1.0 or commit-based versions require justification -- Discuss with maintainers if unsure - -**Example of dependency versioning preferences:** -```go -// Most preferred: Stable semantic version -require github.com/example/lib v1.2.3 - -// Acceptable if no stable alternative: Pre-1.0 version -require github.com/example/lib v0.6.2 - -// Least preferred: Commit-based (sometimes unavoidable for indirect deps) -require github.com/example/lib v0.0.0-20230101120000-abcdef123456 -``` - -### Dependencies in Different Contexts - -**Production code** (`prometheus/`, `prometheus/...`): -- **Strictest requirements** - affects all users -- Prefer stable versions (v1.0.0+) when available -- Requires maintainer approval -- Minimal transitive dependencies preferred -- Every dependency must be justified - -**Test code** (`*_test.go`): -- **More flexible** - test dependencies don't propagate to users! -- Go modules improved in recent versions: test deps stay in our `go.mod` but don't leak to importers -- Can use testing libraries (`go.uber.org/goleak`, `github.com/google/go-cmp`, etc.) -- Still prefer stable, well-maintained tools - -**Examples** (`examples/` directory): -- **Most flexible** - example code isn't imported by users -- Can use newer versions to demonstrate features -- Still prefer stable dependencies when possible -- **Warning:** Example imports CAN cause indirect deps to appear in `go.mod` - -**Real example discovered in dependency analysis:** - -The file `api/prometheus/v1/example_test.go` imports `prometheus/common/config`. Testing -showed that this single import causes many transitive dependencies to appear in our `go.mod`. -Removing such imports would clean up numerous indirect dependencies because those packages -aren't actually used in the production code path. - -### Dependency Update Process - -When updating dependencies, use standard Go module workflows: - -**Checking and updating:** -```bash -# Check for available updates -go list -u -m all - -# Update a specific dependency -go get -u github.com/prometheus/client_model@v0.6.2 - -# Clean up and verify -go mod tidy -go mod verify -``` - -**Understanding dependency usage:** -```bash -# See why a package is in our dependencies -go mod why github.com/some/package - -# See the full dependency graph -go mod graph -``` - -**Testing:** -```bash -make test -``` - -**Before committing:** -- Run the full test suite -- Review the dependency's CHANGELOG for breaking changes -- Verify `go.sum` changes are reasonable - -**Consider the impact:** -- Will the update affect downstream projects (like Kubernetes)? -- Does it introduce new transitive dependencies? -- Are there any security advisories for the old or new version? - -### Red Flags: Dependencies to Reject - -When evaluating dependencies, be cautious of: - -- Known unpatched security vulnerabilities -- Incompatible licenses (must be Apache 2.0 compatible) -- Excessive transitive dependencies -- Unmaintained projects (no recent activity or releases) -- Poor maintenance indicators (unresponsive maintainers, accumulating issues) - -Discuss with maintainers if a dependency raises concerns. - -### Example Dependency Evaluation - -When proposing a new dependency in an issue, provide analysis covering: - -**Basic information:** -- Purpose and which component needs it -- Version and maintenance status -- License compatibility - -**Impact analysis:** -- Will it propagate to users? (consider which code imports it) -- How many transitive dependencies does it bring? -- Any security concerns? - -**Alternatives:** -- What alternatives were considered? -- Why are they not suitable? - -**Recommendation:** -- Your assessment with justification - -Maintainers will review and provide feedback before you proceed with implementation. - ### Resources - [Go Modules Reference](https://go.dev/ref/mod) - comprehensive but dense - [Semantic Versioning](https://semver.org/) - [Go Security Best Practices](https://go.dev/security/best-practices) - -### Key Takeaways - -1. **Indirect dependencies don't always propagate** - Go modules are smart about this -2. **Test dependencies don't leak** to projects that import us -3. **Example code can leak dependencies** - be careful with example imports -4. **We're part of the Kubernetes dependency chain** - our choices have wide impact -5. **When in doubt, discuss first** - open an issue before adding dependencies