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
204 changes: 204 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# AGENTS.md

## Docs Index

Detailed domain-specific guidelines are in these files -- read them before working in the corresponding area:

- [docs/security-guidelines.md](docs/security-guidelines.md) -- Container security, RBAC, TLS, network policies, webhook security
- [docs/performance-guidelines.md](docs/performance-guidelines.md) -- Cache architecture, watch predicates, reconciliation patterns, drift detection
- [docs/error-handling-guidelines.md](docs/error-handling-guidelines.md) -- ReconcileError types, retry logic, status conditions, logging
- [docs/api-contracts-guidelines.md](docs/api-contracts-guidelines.md) -- CRD types, kubebuilder markers, CEL validation, code generation
- [docs/testing-guidelines.md](docs/testing-guidelines.md) -- Unit/API/E2E test tiers, frameworks, test helpers, CI integration
- [docs/integration-guidelines.md](docs/integration-guidelines.md) -- Bindata pattern, cert-manager, OpenShift platform, Bitwarden plugin

## Project Overview

This is a Kubernetes operator (built with kubebuilder/controller-runtime) that deploys and manages the upstream [external-secrets](https://github.com/openshift/external-secrets) application on OpenShift clusters. The operator does not embed upstream code -- it manages upstream resources as static YAML manifests (bindata) applied imperatively.

Three controllers run in a single binary:

| Controller | Package | Watches | Purpose |
|---|---|---|---|
| `external-secrets-controller` | `pkg/controller/external_secrets/` | `ExternalSecretsConfig` CR + all managed resources | Installs/reconciles operand deployments, RBAC, services, webhooks, network policies |
| `external-secrets-manager` | `pkg/controller/external_secrets_manager/` | `ExternalSecretsManager` CR + `ExternalSecretsConfig` status | Aggregates controller statuses into a global status CR |
| `crd-annotator` | `pkg/controller/crd_annotator/` | ESO CRDs + `ExternalSecretsConfig` | Adds cert-manager CA injection annotations to operand CRDs (conditional) |

## Project Structure

```
api/v1alpha1/ CRD type definitions, conditions, shared types, deepcopy
api/v1alpha1/tests/ Declarative YAML test suites for CRD validation
bindata/ Static operand YAML manifests (compiled into Go via go-bindata)
cmd/ Operator entrypoint (main.go)
config/ Kustomize manifests (CRDs, RBAC, manager, samples, bundle)
hack/ Shell scripts (codegen, verification, CI helpers)
images/ci/ CI Dockerfiles (coverage-instrumented builds)
pkg/controller/ Controller implementations
client/ CtrlClient interface + counterfeiter fakes
common/ Shared utilities (errors, constants, decode helpers, drift detection)
commontest/ Shared test fixtures (TestExternalSecretsConfig, TestExternalSecretsManager)
crd_annotator/ CRD annotation controller
external_secrets/ Main operand reconciliation controller
external_secrets_manager/ Status aggregation controller
pkg/operator/ Manager setup, controller registration
pkg/version/ Build-time version info (ldflags)
test/apis/ API integration tests (Ginkgo + envtest)
test/e2e/ End-to-end tests (Ginkgo + live cluster)
test/utils/ E2E test helpers
tools/ Go module for build-time tool dependencies
```
Comment on lines +28 to +49

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifier to the code fence.

The directory structure code block should specify a language identifier (e.g., text or plaintext) for proper rendering and linting compliance.

📝 Proposed fix
-```
+```text
 api/v1alpha1/           CRD type definitions, conditions, shared types, deepcopy
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 28-28: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 28 - 49, The fenced directory-listing block in
AGENTS.md is missing a language identifier; update the opening triple-backticks
that precede the directory list (the block containing lines like "api/v1alpha1/ 
CRD type definitions...") to include a plaintext identifier (e.g., ```text) so
the code fence renders and lints correctly; ensure only the first backtick fence
is changed and the closing fence remains ``` unchanged.


## Go Workspace and Module Layout

The repo uses `go.work` with four modules: `.`, `./cmd/external-secrets-operator`, `./test`, `./tools`. This means:

- `GOFLAGS` is cleared for test and fmt targets to avoid `-mod=vendor` conflicting with `go.work`.
- When adding a dependency, use `make update-dep PKG=pkg@version` to update across all modules, then `make update-vendor`.
- Vendoring is workspace-level (`go work vendor`), not per-module.
- All build-time tools (controller-gen, golangci-lint, ginkgo, etc.) are vendored and built from source via `go build -mod=vendor`.

## Build System (Key Makefile Targets)

| Target | What it does |
|---|---|
| `make build` | Full build: generate + manifests + fmt + vet + compile binary |
| `make build-operator` | Compile binary only (no codegen, fastest iteration) |
| `make test` | Run unit + API integration tests (no cluster needed) |
| `make test-unit` | Unit tests only |
| `make test-apis` | API validation tests via envtest |
| `make test-e2e` | E2E tests against a live cluster |
| `make lint` | Run golangci-lint with all configured linters |
| `make lint-fix` | Run golangci-lint with auto-fix |
| `make update` | Full regeneration: codegen + manifests + operand manifests + bindata + bundle + docs |
| `make verify` | Run vet + fmt + verify-deps + verify-bindata + verify-generated + govulncheck + check-git-diff |
| `make update-vendor` | Update vendor directory across all workspace modules |
| `make update-dep PKG=x@v` | Update a single dependency across all modules |
| `make manifests` | Regenerate CRD YAML, RBAC, webhook configs from kubebuilder markers |
| `make generate` | Regenerate DeepCopy methods |
| `make docs` | Regenerate API reference docs |
| `make clean` | Remove bin/, _output/, cover.out |

After any code change, run `make update && make verify` to ensure all generated files are consistent. CI runs `check-git-diff` which fails if generated files are stale.

## Code Style and Formatting

### Import Order

Imports must follow the order enforced by `gci` in `.golangci.yml`:

1. Standard library
2. Third-party packages
3. `github.com/openshift/external-secrets-operator` (project-local)
4. Blank imports, dot imports, aliases, local module

### Linting

The repo uses golangci-lint v2 with a comprehensive set of linters (see `.golangci.yml`). Key rules:

- **depguard** blocks `math/rand` (use `math/rand/v2`), `github.com/sirupsen/logrus`, and `github.com/pkg/errors` (use `errors`/`fmt`).
- **kubeapilinter** runs only on `api/v1alpha1/*` files. Use `//nolint:kubeapilinter` with a comment for intentional deviations.
- **golines** max line length is 200 characters.
- **gofmt** rewrites `interface{}` to `any` and `a[b:len(a)]` to `a[b:]`.
- Generated files are excluded in `lax` mode.
- Test files have relaxed rules for `gocyclo`, `errcheck`, `gosec`, `forcetypeassert`, and others.

### File Headers

All `.go` files must include the Apache 2.0 license header from `hack/boilerplate.go.txt` (year 2025).

### FIPS Build

Production builds use `hack/go-fips.sh` which enables `GOEXPERIMENT=strictfipsruntime` and build tags `strictfipsruntime,openssl` when the Go compiler supports it. Local dev builds without FIPS work but cannot be used in CI/production.

## Naming Conventions

### Go Packages

Controller packages use `snake_case`: `external_secrets`, `external_secrets_manager`, `crd_annotator`. This matches kubebuilder conventions.

### Constants

- Controller names: `"external-secrets-controller"`, `"external-secrets-manager"`, `"crd-annotator"` (kebab-case in strings).
- Finalizer format: `<crd-plural>.<api-group>/<controller-name>` (e.g., `externalsecretsconfigs.operator.openshift.io/external-secrets-controller`).
- Asset name constants: `<resourceKind>_<resourceName>AssetName` in camelCase (e.g., `controllerDeploymentAssetName`).
- Environment variable constants: all-caps with suffix `EnvVarName` (e.g., `externalSecretsImageEnvVarName`).

### Bindata YAML Files

Files in `bindata/external-secrets/resources/` follow the pattern: `<kind-lowercase>_<resource-name>.yml` (e.g., `deployment_external-secrets.yml`, `clusterrole_external-secrets-controller.yml`). Network policies in `bindata/external-secrets/` use `.yaml` extension.

### CRD Object Names

Both `ExternalSecretsConfig` and `ExternalSecretsManager` are singletons named `"cluster"` (enforced by CEL). Constants `ExternalSecretsConfigObjectName` and `ExternalSecretsManagerObjectName` in `pkg/controller/common/constants.go` hold this value.

## Architectural Patterns

### Reconciler Structure

Every controller follows the same pattern:

1. A `Reconciler` struct embedding `operatorclient.CtrlClient`.
2. A `New(ctx, mgr)` constructor that builds the reconciler and client(s).
3. A `SetupWithManager(mgr)` method that wires watches, predicates, and map functions.
4. A `Reconcile(ctx, req)` method that fetches the primary CR and delegates to `processReconcileRequest`.
5. An `updateStatus(ctx, obj)` method using `retry.RetryOnConflict`.

### CtrlClient Interface

All controllers interact with Kubernetes through `pkg/controller/client.CtrlClient`, not the raw `controller-runtime client.Client`. This interface adds `UpdateWithRetry`, `StatusUpdate`, and `Exists` methods. Unit tests use counterfeiter-generated fakes (`pkg/controller/client/fakes/`).

To regenerate fakes after changing the interface: `go generate ./pkg/controller/client/...`

### Resource Reconciliation Pattern

For each resource type (deployments, services, RBAC, etc.), the same flow is used:

1. Decode static YAML from bindata (`common.Decode*ObjBytes(assets.MustAsset(...))`).
2. Mutate the decoded object (set namespace, labels, annotations, images, env vars, security context).
3. Check existence with `r.Exists()`.
4. If new: `r.Create()`. If existing: compare with `common.HasObjectChanged()`, then `r.UpdateWithRetry()` if drifted.
5. Record Kubernetes events for create/update operations.
6. Wrap errors with `common.FromClientError()`.

### Conditional Resources

Resources that depend on CR configuration use a slice of `{assetName string, condition bool}` structs. Only assets with `condition: true` are applied. Follow this pattern when adding new conditionally-created resources.

## Common Pitfalls

1. **Never return both `RequeueAfter` and a non-nil error** from `Reconcile`. Return one or the other.
2. **Never edit generated files by hand**: `zz_generated.deepcopy.go`, `config/crd/bases/*.yaml`, `pkg/operator/assets/bindata.go`, `config/rbac/role.yaml`, `docs/api_reference.md`. Always use `make update`.
3. **Always run `make update && make verify`** after any code change. CI will reject PRs with stale generated files.
4. **Use the cached client for managed resources** (those with `app: external-secrets` label). Use the uncached client only for external resources like cert-manager Issuers or user-provided Secrets.
5. **Add new watched resources to both `controllerManagedResources` and `buildCacheObjectList()`** in `pkg/controller/external_secrets/controller.go`.
6. **Add new resource types to `HasObjectChanged`** in `pkg/controller/common/utils.go` with field-level comparison, not full `DeepEqual`.
7. **Decode helpers panic on failure** -- this is intentional for build-time-constant assets. Do not add error handling around `Decode*ObjBytes` calls.
8. **RBAC markers** (`+kubebuilder:rbac`) go in `pkg/controller/external_secrets/controller.go` for the operator's own permissions. Operand RBAC is in static YAML under `bindata/`.
9. **The `go.work` workspace** means you cannot use `-mod=vendor` for `go test` or `go fmt`. The Makefile already clears `GOFLAGS` for affected targets.
10. **Container tool defaults to `podman`** (`CONTAINER_TOOL ?= podman`), not Docker. Override with `CONTAINER_TOOL=docker` if needed.

## PR and Contribution Expectations

- Run `make lint` and `make test` locally before submitting.
- Run `make verify` to ensure generated files are in sync.
- Add unit tests for new reconciliation logic using table-driven tests and `FakeCtrlClient`.
- Add API test cases in `api/v1alpha1/tests/<crd>/` for any new CRD field or validation rule.
- Add E2E test cases with appropriate Ginkgo labels for platform-specific tests.
- Follow existing error wrapping patterns: `common.FromClientError` for API calls, `common.NewIrrecoverableError` for config validation failures.
- Commit messages should reference the relevant Jira ticket (e.g., `OCPBUGS-12345: description`).
- PR reviewers/approvers are listed in `OWNERS`.

## Environment Variables

The operator reads these at runtime (typically set by OLM/CSV):

| Variable | Purpose |
|---|---|
| `RELATED_IMAGE_EXTERNAL_SECRETS` | Container image for the external-secrets operand |
| `RELATED_IMAGE_BITWARDEN_SDK_SERVER` | Container image for the Bitwarden SDK server |
| `OPERAND_EXTERNAL_SECRETS_IMAGE_VERSION` | Version label for operand resources |
| `BITWARDEN_SDK_SERVER_IMAGE_VERSION` | Version label for Bitwarden resources |
| `OPERATOR_IMAGE_VERSION` | Operator version string |
| `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` | Proxy fallback from OLM environment |

For local development, set at minimum `RELATED_IMAGE_EXTERNAL_SECRETS` via `t.Setenv()` in tests or shell export for `make run`.
51 changes: 51 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# CLAUDE.md

@AGENTS.md

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify or remove the @AGENTS.md annotation.

The @AGENTS.md syntax is not standard Markdown. If this is intended as a reference or link, use a proper Markdown link: [AGENTS.md](AGENTS.md). If it's meant for a specific tool or extension, add a comment explaining the syntax.

🔗 Proposed fix
 # CLAUDE.md
 
-@AGENTS.md
+See [AGENTS.md](AGENTS.md) for comprehensive architectural details.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@AGENTS.md
# CLAUDE.md
See [AGENTS.md](AGENTS.md) for comprehensive architectural details.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` at line 3, Replace the nonstandard annotation '`@AGENTS.md`' in
CLAUDE.md with a proper Markdown link or an explanatory comment; specifically
locate the literal '`@AGENTS.md`' token and either change it to a Markdown link
like [AGENTS.md](AGENTS.md) or, if it is tool-specific, add a short inline
comment explaining the syntax and its purpose so readers know why it isn't
standard Markdown.


## Build and Test Commands

When working in this repository, use these commands:

### Before Committing
```bash
make update && make verify
```
This regenerates all code, manifests, and bindata, then runs verification checks. CI will reject PRs with stale generated files.

### Development Workflow
```bash
make build # Full build with codegen
make build-operator # Fast rebuild (binary only, no codegen)
make test # Run unit + API tests (no cluster needed)
make lint # Run golangci-lint
make lint-fix # Auto-fix linting issues
```

### Testing
```bash
make test-unit # Unit tests only
make test-apis # API validation tests (envtest)
make test-e2e # E2E tests (requires cluster)
```

### Dependency Management
```bash
make update-vendor # Sync vendor across all workspace modules
make update-dep PKG=package@version # Update a dependency in all modules
```

## Claude Code Preferences

- Always run `make update && make verify` after code changes that affect:
- CRD definitions (`api/v1alpha1/`)
- Kubebuilder markers (`+kubebuilder:*`)
- Bindata YAML (`bindata/`)
- Generated code triggers

- Use `make lint-fix` to automatically fix formatting and linting issues before suggesting manual fixes

- The repository uses a Go workspace (`go.work`) with 4 modules. Never manually edit `GOFLAGS` or use `-mod=vendor` in test/fmt commands — the Makefile handles this

- Container builds default to `podman`. Override with `CONTAINER_TOOL=docker` if needed

- All build-time tools are vendored. Do not suggest installing tools globally
Loading