Skip to content
Open
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
116 changes: 116 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,119 @@ 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`.

**Example from our codebase:**
- [`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 `promslog/flag` anywhere.

**Testing this:**
Copy link
Member

Choose a reason for hiding this comment

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

This is great, but either we add more specific info or even script for this or we remove this. It's a bit fuzzy e.g.

# See why a package is in our dependencies - is this from client_golang repo? Or from repo that imports client_golang?

Test if a dependency propagates by creating a test module that imports client_golang
and checking if the dependency appears in its go.mod

Kind of out of place? Can we give more specific output?

It feels a script would help?

Copy link
Author

Choose a reason for hiding this comment

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

Updated inline. Let me know if you meant to be in a separate script ?


To verify a dependency doesn't propagate to users, create a test module:

```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)
```

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

**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 adding 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.

### 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)
Loading