|
| 1 | +# edp-sonar-operator |
| 2 | + |
| 3 | +`github.com/epam/edp-sonar-operator` — a Go kubebuilder operator that configures an **existing** SonarQube instance declaratively via Kubernetes CRDs. It does not install SonarQube; it reconciles its state. Quality gates defined here are the blocking gates enforced in every KRCI Tekton CI pipeline. |
| 4 | + |
| 5 | +CRD group: `edp.epam.com/v1alpha1` — `Sonar`, `SonarProject`, `SonarQualityGate`, `SonarQualityProfile`, `SonarUser`, `SonarGroup`, `SonarPermissionTemplate`. |
| 6 | + |
| 7 | +## Build & Test |
| 8 | + |
| 9 | +```bash |
| 10 | +make build # fmt + vet + compile → dist/manager-<arch> |
| 11 | +make test # unit + envtest integration tests (downloads envtest binaries on first run) |
| 12 | +make lint # golangci-lint (config: .golangci.yaml) |
| 13 | +make lint-fix # golangci-lint with auto-fix |
| 14 | +``` |
| 15 | + |
| 16 | +Run a single test package: |
| 17 | + |
| 18 | +```bash |
| 19 | +go test ./internal/controller/qualitygate/... -v |
| 20 | +``` |
| 21 | + |
| 22 | +Integration tests against a live SonarQube (optional): |
| 23 | + |
| 24 | +```bash |
| 25 | +TEST_SONAR_URL=http://localhost:9000 TEST_SONAR_USER=admin TEST_SONAR_PASSWORD=admin make test |
| 26 | +``` |
| 27 | + |
| 28 | +## Code Generation |
| 29 | + |
| 30 | +After editing kubebuilder markers in `api/v1alpha1/` or controller RBAC comments: |
| 31 | + |
| 32 | +```bash |
| 33 | +make generate # regenerates DeepCopy methods (zz_generated.deepcopy.go) |
| 34 | +make manifests # regenerates CRDs → deploy-templates/crds/ and config/crd/bases/, also runs api-docs |
| 35 | +make mocks # regenerates mockery mocks in pkg/client/sonar/mocks/ |
| 36 | +``` |
| 37 | + |
| 38 | +CRD YAML files in `deploy-templates/crds/` and `config/crd/bases/` are generated — do not edit manually. Same for `api/v1alpha1/zz_generated.deepcopy.go` and `api/common/zz_generated.deepcopy.go`. |
| 39 | + |
| 40 | +## Architecture |
| 41 | + |
| 42 | +``` |
| 43 | +cmd/main.go |
| 44 | + └── creates controller-runtime Manager, registers all 7 reconcilers |
| 45 | +
|
| 46 | +api/v1alpha1/ — CRD type definitions (spec, status, kubebuilder markers) |
| 47 | +api/common/ — shared SonarRef and HasSonarRef interface |
| 48 | +
|
| 49 | +internal/controller/<resource>/ |
| 50 | + ├── sonar*_controller.go — Reconcile() entry: fetches CR, resolves Sonar client, |
| 51 | + │ delegates to chain; handles finalizer + deletion |
| 52 | + └── chain/ |
| 53 | + ├── chain.go — handler interface + sequential chain runner |
| 54 | + ├── factory.go — MakeChain() wires handlers in order |
| 55 | + ├── create_*.go — idempotent create/update step |
| 56 | + ├── sync_*.go — sync sub-resources (conditions, rules, groups, perms) |
| 57 | + └── remove_*.go — deletion step (invoked on DeletionTimestamp) |
| 58 | +
|
| 59 | +pkg/client/sonar/ |
| 60 | + ├── sonar.go — Client struct (resty-based), base HTTP methods |
| 61 | + ├── client_interface.go — ClientInterface composed of sub-interfaces (hand-written, NOT generated) |
| 62 | + ├── quality_gate.go — QualityGateClient methods |
| 63 | + ├── quality_profile.go — QualityProfileClient methods |
| 64 | + ├── group.go / user.go / project.go / permission_template.go / rule.go / system.go |
| 65 | + ├── provider.go — ApiClientProvider: resolves Client from Sonar CR + K8s Secret |
| 66 | + └── mocks/ — mockery-generated mocks (do not edit) |
| 67 | +
|
| 68 | +deploy-templates/ — Helm chart (values.yaml, crds/, templates/); README auto-generated by helm-docs |
| 69 | +config/ — Kustomize manifests (crd, rbac, manager, webhook, certmanager, prometheus) |
| 70 | +bundle/ — OLM bundle manifests for OperatorHub |
| 71 | +``` |
| 72 | + |
| 73 | +## Key Conventions |
| 74 | + |
| 75 | +**SonarRef dependency model.** Every child CR (`SonarQualityGate`, `SonarUser`, etc.) has a `spec.sonarRef.name` pointing to a `Sonar` CR. Reconcilers call `ApiClientProvider.GetSonarApiClientFromSonarRef`, which checks `sonar.Status.Connected == true` before building the HTTP client. If the parent `Sonar` is not yet connected, the child requeues after 30 s. |
| 76 | + |
| 77 | +**Chain of responsibility.** Each controller delegates work to a `chain.MakeChain(sonarApiClient)` call. To add a new reconciliation step, implement `ServeRequest(ctx, *sonarApiCRType) error` and append it in `chain/factory.go`. All handlers are tested individually with mock clients. |
| 78 | + |
| 79 | +**SonarQube REST client is hand-written, not generated.** `pkg/client/sonar/sonar.go` and sibling files wrap resty with idiomatic Go. `ClientInterface` in `client_interface.go` is the composition interface used by all chain handlers. Mocks in `mocks/` are generated from `ClientInterface` via mockery (`.mockery.yaml`). |
| 80 | + |
| 81 | +**Credentials.** The `Sonar` CR's `spec.secret` names a K8s Secret with `user` and `password` keys. The client is constructed with basic auth against `spec.url`. |
| 82 | + |
| 83 | +**Name field is immutable post-creation.** `SonarQualityGate.spec.name` (and analogous fields on other CRDs) drives the SonarQube resource name. Changing it after creation causes the old resource to be deleted and a new one created. |
0 commit comments