This document provides guidance for developers working on the EDP Keycloak Operator project.
- Prerequisites
- Quick Start
- Project Structure
- Development Workflow
- Testing
- Building and Deployment
- Running and Debugging Locally
- Common Development Tasks
- Getting Help
Before starting development, ensure you have the following tools installed:
| Tool | Purpose |
|---|---|
| Go (1.25+) | Programming language |
| Docker/Podman | Container runtime |
| kubectl | Kubernetes CLI |
| kind | Local Kubernetes cluster |
| Helm | Package manager |
| make | Build automation |
| Tool | Purpose |
|---|---|
| operator-sdk | Operator development framework |
| kuttl | E2E testing |
Note: The following tools are automatically installed by the Makefile when needed:
golangci-lint- Code lintingmockery- Mock generationcontroller-gen- Code generationkustomize- Kubernetes manifest managementhelm-docs- Helm documentation generationcrdoc- CRD documentation generation
Get started with development:
# 1. Clone the repository
git clone https://github.com/epam/edp-keycloak-operator.git
cd edp-keycloak-operator
# 2. Start local Kubernetes cluster
make start-kind
# 3. Install CRDs
make install
# 4. Start test Keycloak instance
make start-keycloak
# 5. Run tests
TEST_KEYCLOAK_URL=http://localhost:8086 make test
# 6. Build the operator
make buildedp-keycloak-operator/
├── api/ # API definitions
│ ├── v1/ # v1 API version
│ └── v1alpha1/ # v1alpha1 API version
├── bin/ # Downloaded development tools
├── bundle/ # OLM bundle manifests
├── cmd/ # Main application entry point
├── config/ # Kubernetes manifests
├── deploy-templates/ # Helm chart templates
├── docs/ # Documentation
├── hack/ # Development scripts
├── internal/ # Private application code
│ └── controller/ # Controller implementations
├── pkg/ # Public library code
│ ├── client/
│ │ └── keycloakapi/ # oapi-codegen generated client
│ ├── secretref/ # Secret reference utilities
│ └── util/ # Utility functions
└── tests/ # E2E test files
# 1. Create feature branch
git checkout -b feature/new-feature
# 2. Make changes
# Edit code files
# 3. Generate code
make manifests
make generate
make mocks
# 4. Run tests
make test
# 5. Run linter
make lint-fix
# 6. Commit (project requires one commit per PR for clean history)
git add .
git commit -m "feat: add new feature (#issue_number)"
# For subsequent changes, amend the existing commit:
git add .
git commit --amend --no-edit
# Push changes (--force-with-lease is safer than --force as it prevents overwriting others' work)
git push --force-with-leaseThe project uses make test as the main testing command, which intelligently runs different test types based on environment variables.
Unit tests run by default and don't require external dependencies:
# Run unit tests only (integration tests will be skipped)
make test
# Run tests for specific package
go test ./pkg/client/keycloak/...
# Run tests with verbose output
go test -v ./...Integration tests run automatically when TEST_KEYCLOAK_URL is specified. They require:
- Keycloak instance - Provides the Keycloak server for testing
- envtest - Kubernetes API server for testing controller logic
- Ginkgo - BDD testing framework (github.com/onsi/ginkgo)
- Gomega - Ginkgo's preferred matcher library (github.com/onsi/gomega)
# Start test Keycloak instance
make start-keycloak
# Run unit tests + integration tests
TEST_KEYCLOAK_URL="http://localhost:8086" make testHow it works: The make test command checks if TEST_KEYCLOAK_URL is set:
- Without
TEST_KEYCLOAK_URL: Runs only unit tests, shows warning that integration tests are skipped - With
TEST_KEYCLOAK_URL: Runs both unit tests and integration tests
End-to-end tests require a Kubernetes cluster and use the following tools:
- kind - Local Kubernetes cluster for testing
- kuttl - Kubernetes testing framework for e2e tests
- Docker/Podman - Container runtime for building and loading test images
# Start kind cluster
make start-kind
# Run e2e tests
make e2e
# Clean up resources
make delete-kind# Build binary
make build
# Build container image
docker build -t keycloak-operator:latest .# Generate Helm documentation
make helm-docs
# Validate Helm chart
helm lint deploy-templates/# Generate a new bundle version
VERSION=1.29.0 CHANNELS="stable" DEFAULT_CHANNEL=stable make bundleThis section covers how to run and debug the operator locally during development.
Before running the operator locally, ensure you have:
-
Kubernetes Cluster: The operator needs to connect to a Kubernetes cluster using the current active context from your kubeconfig.
-
Keycloak Instance: A running Keycloak server for the operator to manage.
# Start a local kind cluster
make start-kind# Install Custom Resource Definitions to the cluster
make install# Start a local Keycloak server (runs on port 8086)
make start-keycloakThe operator requires the following environment variables:
WATCH_NAMESPACE: Namespace where the operator will reconcile resources (empty string means all namespaces)OPERATOR_NAMESPACE: Namespace used for cluster resources to get secrets with credentials
# Example: Run operator watching all namespaces, using default namespace for secrets
export WATCH_NAMESPACE=""
export OPERATOR_NAMESPACE="default"
# Or watch only a specific namespace
export WATCH_NAMESPACE="default"
export OPERATOR_NAMESPACE="default"# Set environment variables and run
WATCH_NAMESPACE="" OPERATOR_NAMESPACE="default" go run cmd/main.goUse the preconfigured VS Code launch configuration named local:
- Open VS Code in the project root
- Go to Run and Debug (Ctrl/Cmd + Shift + D)
- Select "local" configuration
- Press F5 to start debugging
Create a Keycloak resource and secret for testing:
apiVersion: v1.edp.epam.com/v1
kind: Keycloak
metadata:
name: keycloak-sample
spec:
secret: keycloak-access
url: http://host.docker.internal:8086 # For Docker Desktop
# Alternative URL:
# url: http://192.168.0.146:8086 # Use your local IP
---
apiVersion: v1
kind: Secret
metadata:
name: keycloak-access
data:
username: YWRtaW4= # admin (base64 encoded)
password: YWRtaW4= # admin (base64 encoded)Apply the resources:
kubectl apply -f keycloak-sample.yamlCheck that the Keycloak resource is properly reconciled:
# Check Keycloak resource status
kubectl get keycloak keycloak-sample
# View detailed status
kubectl describe keycloak keycloak-sampleThe status should show connected: true when the operator successfully connects to Keycloak.
If host.docker.internal doesn't work, get your local IP address:
# macOS/Linux
ipconfig getifaddr $(route get default | awk '/interface: / {print $2}')
# Alternative for macOS
ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | head -1- Connection Refused: Ensure Keycloak is running on the expected port (8086)
- CRD Not Found: Run
make installto install CRDs - Permission Denied: Ensure your kubeconfig has proper permissions
- Namespace Issues: Verify
OPERATOR_NAMESPACEexists and contains the secret
Check the following when diagnosing issues:
- The operator logs will appear in your terminal or VS Code debug console
- Use
kubectl logsto check other cluster resources - Set log level with
--zap-log-level=debugfor more verbose output
This section covers typical development workflows for extending the operator.
When adding new functionality to existing Custom Resources like KeycloakClient:
# 1. Add the new field to the resource spec
# Edit api/v1/keycloakclient_types.go
# Add your field to KeycloakClientSpec struct
# 2. Generate code and manifests
make generate # Updates deepcopy methods
make manifests # Updates CRD YAML files
# 3. Implement business logic
# Edit files in internal/controller/keycloakclient/chain/
# Map your new field to the corresponding Keycloak client configuration
# 4. Update Keycloak client methods (if needed)
# Edit files in pkg/client/keycloak/
# Add new methods to interact with Keycloak API
# 5. Generate mocks for testing
make mocks
# 6. Write comprehensive tests
# Unit tests: Add to relevant *_test.go files
# Integration tests: Add to keycloakclient_controller_integration_test.go
# 7. Run linter and fix issues
make lint-fix
# 8. Test your changes
TEST_KEYCLOAK_URL="http://localhost:8086" make test
# 9. Commit and push
git add .
git commit -m "feat(KeycloakClient): add new field to KeycloakClient (#issue_number)"
git push --force-with-lease- API Definition:
api/v1/keycloakclient_types.go- Add the field to the spec - Controller Logic:
internal/controller/keycloakclient/chain/- Implement the business logic - Keycloak Client:
pkg/client/keycloak/- Add API methods if needed - Tests: Write both unit and integration tests
For completely new functionality, create new Custom Resources:
# 1. Use operator-sdk to scaffold the new resource
operator-sdk create api \
--group v1.edp.epam.com \
--version v1 \
--kind KeycloakSomeResource \
--resource \
--controller
# 2. Customize the generated files
# Edit api/v1/keycloaksomeresource_types.go - Define your resource spec and status
# Edit internal/controller/keycloaksomeresource/ - Implement controller logic
# 3. Generate code and manifests
make generate
make manifests
# 4. Add RBAC permissions
# Add kubebuilder RBAC markers to your controller file:
# // +kubebuilder:rbac:groups=v1.edp.epam.com,namespace=placeholder,resources=keycloaksomeresources,verbs=get;list;watch;create;update;patch;delete
# // +kubebuilder:rbac:groups=v1.edp.epam.com,namespace=placeholder,resources=keycloaksomeresources/status,verbs=get;update;patch
# // +kubebuilder:rbac:groups=v1.edp.epam.com,namespace=placeholder,resources=keycloaksomeresources/finalizers,verbs=update
# Run 'make manifests' to generate config/rbac/ files
# Manually update deploy-templates/templates/ to align with generated RBAC
# 5. Implement Keycloak integration
# Add methods to pkg/client/keycloak/ for your resource
# 6. Generate mocks and write tests
make mocks
# Write unit tests for controller logic
# Write integration tests following existing patterns
# 7. Add CRD examples
# Create example YAML in deploy-templates/_crd_examples/
# 8. Test and validate
make lint-fix
TEST_KEYCLOAK_URL="http://localhost:8086" make test
# 9. Commit your work
git add .
git commit -m "feat(KeycloakSomeResource): add KeycloakSomeResource controller (#issue_number)"
git push --force-with-lease- Operator SDK: Building Operators with Go
- Controller Runtime: Kubebuilder Book
- RBAC Markers: Controller Runtime RBAC
When updating business logic for existing resources:
# 1. Identify the chain element to modify
# Controllers use a chain pattern in internal/controller/*/chain/
# 2. Make your changes
# Edit the appropriate chain file (e.g., put_client.go, put_client_role.go)
# 3. Update tests
# Modify existing unit tests
# Add new test cases for your changes
# 4. Test thoroughly
make lint-fix
TEST_KEYCLOAK_URL="http://localhost:8086" make test
# 5. Commit changes
git add .
git commit -m "fix: update client role logic (#issue_number)"
git push --force-with-lease- Always run code generation: Use
make generateandmake manifestsafter API changes - Use RBAC markers: Add kubebuilder RBAC markers to controllers, don't edit
config/rbac/manually - Sync Helm RBAC: After generating RBAC with
make manifests, manually aligndeploy-templates/templates/RBAC - Test thoroughly: Write both unit and integration tests for new functionality
- Follow naming conventions: Use consistent naming patterns for fields and methods
- Document your changes: Add comments to complex logic and update examples
- Validate with real Keycloak: Test against actual Keycloak instance, not just mocks
- Issues: Create GitHub issues for bugs and feature requests
- Code Examples: Review test files for usage patterns and CRD examples in
deploy-templates/_crd_examples/