Skip to content

sourcehawk/operator-api-mirrorer

Repository files navigation

Operator API Mirrorer

This is a small utility that extracts the public API types from upstream Kubernetes operators (e.g., OpenTelemetry Operator, ECK) and republishes them as version-pinned, standalone Go modules.

This allows downstream consumers to depend on operator APIs in a reproducible, conflict-free way, without inheriting the upstream operator's dependency tree or Go module drift.


✨ Why this project exists

Many Kubernetes operators ship their CRD Go types inside complex monolithic repositories. Importing those API types directly often pulls in:

  • Huge dependency graphs
  • Unwanted Kubernetes version constraints
  • Internal packages that are not meant to be consumed
  • Frequent breaking changes as upstream moves fast
  • Non-reproducible builds when upstream bumps its own dependencies

operator-api-mirrorer fixes this.

It produces stable, minimal, isolated Go modules containing only:

  • the selected API directories (e.g. apis/*, pkg/apis/elasticsearch/*)
  • the internal packages required to build them
  • rewritten imports so the API code is self-contained
  • a minimal go.mod with just the required dependencies
  • optional overrides for Kubernetes versions or other modules

This guarantees reproducible builds while allowing consumers to use exactly the API types they need.


🧩 How it works

For each operator defined in operators.yaml, the tool:

  1. Clones the upstream repository at the specified version.
  2. Copies the requested API directories and filters out all non-Go files and test files.
  3. Parses API imports to discover and copy required internal packages.
  4. Rewrites import paths to point to the mirror module.
  5. Generates a fresh go.mod based on upstream requirements.
  6. Applies dependency overrides from operators.yaml using replace.
  7. Runs go mod tidy to prune unused dependencies.
  8. Writes the final result to:
mirrors/<slug>/

Each mirrored API version is a fully standalone Go module. Upgrading causes an overwrite of the existing mirror, therefore you should create a git tag (mirrors//) for each mirror you create.


📦 Example: operators.yaml

operators:
  - slug: otel-operator
    repo: github.com/open-telemetry/opentelemetry-operator
    currentVersion: v0.138.0
    goModPath: go.mod
    apiPaths:
      - "apis/*"
    overwriteDependencies:
      - name: k8s.io/api
        version: v0.32.10
      - name: k8s.io/apimachinery
        version: v0.32.10
      - name: sigs.k8s.io/controller-runtime
        version: v0.20.4

  - slug: eck-operator
    repo: github.com/elastic/cloud-on-k8s
    currentVersion: v3.2.0
    goModPath: go.mod
    apiPaths:
      - "pkg/apis/elasticsearch/*"
    overwriteDependencies:
      - name: k8s.io/api
        version: v0.32.10
      - name: k8s.io/apimachinery
        version: v0.32.10

🚀 Creating your own mirrors

The operator-api-mirrors repository contains ready-made mirrors that do not overwrite or pin dependencies differently from the upstream operators.

If you need custom dependency overrides (for example, to keep your Kubernetes libraries at specific versions different from what the upstream operator uses), you can create your own mirror repository using this Go tool as a library and CLI.

Build / install the tool

From another repo (your own mirror repo) you can install the CLI globally:

go install github.com/sourcehawk/operator-api-mirrorer/cmd/mirrorer@latest

This will put a mirrorer binary in your $GOBIN (usually ~/go/bin).

Or, if you’re working directly inside a clone of this repo:

make build

Run the mirror process

Mirroring operator APIs is done via the mirror subcommand.

At minimum, you must specify your Go module prefix via --gitRepo. This is the base of the module path used when writing the go.mod for each mirrored operator version:

module <gitRepo>/mirrors/otel-operator

Example

./mirrorer mirror --gitRepo="github.com/sourcehawk/operator-api-mirrorer"

This generates mirrored modules under:

mirrors/<operator>/

Example layout:

mirrors/
└── otel-operator/
    ├── apis/
    ├── internal/
    ├── pkg/
    └── go.mod

CLI Structure

The tool uses the following subcommands:

mirrorer mirror   # generate/update mirrors
mirrorer tag      # create tags for mirrored operator versions

mirror command flags

Flag Default Description
--config operators.yaml Path to your operator definitions
--mirrorsPath ./mirrors Output directory for all generated mirrors
--gitRepo required Root Go module path (used inside generated module go.mod files)
--target empty Optional operator slug to mirror only that operator

Full example

./mirrorer mirror \
  --config ./operators.yaml \
  --mirrorsPath ./mirrors \
  --gitRepo github.com/my-org/operator-api-mirror

Tagging mirrored versions

After mirrors have been generated and merged into main, you can create lightweight git tags for each defined operator version using:

./mirrorer tag \
  --config ./operators.yaml \
  --mirrorsPath ./mirrors

This command:

  • reads operators.yaml
  • finds each operator’s current version
  • creates tags of form:
mirrors/<operator>/<version>

Pushing tags is handled separately (e.g., in CI):

git push --tags

⚙️ How dependency overrides work

overwriteDependencies applies replace-only overrides in go.mod:

replace k8s.io/apimachinery => k8s.io/apimachinery v0.32.10
replace k8s.io/api => k8s.io/api v0.32.10

These do not modify upstream requirements; they simply redirect them.

This ensures:

  • consistent Kubernetes versions across all mirrored modules
  • maximum compatibility for downstream controllers
  • no accidental dependency bumps caused by upstream operators

❓ FAQ

Why not just import the upstream operator?

Because:

  • Operators have huge transitive dependency trees
  • Many require pinned Kubernetes versions that break your project
  • They often pull in unrelated internal code
  • They are not intended to be library-friendly

Using a mirror gives you:

  • minimal deps
  • reproducible API versions
  • clean import paths
  • stable builds

Why copy internal packages?

Some operator API code references internal utilities (e.g. ptr, hash, version). We copy only what is needed and rewrite the import paths so everything is self-contained.

Does this violate licensing?

All upstream APIs are open-source (Apache 2.0); the mirror preserves licenses verbatim.


🤝 Contributing

Contributions welcome!


📝 License

Apache 2.0

About

A tool to mirror operator api definitions into a github project

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors