Configuration Reference
Complete reference for DatabaseQueryResource CRD fields
Complete reference for all fields in the DatabaseQueryResource Custom Resource Definition.
CRD Specification
spec.pollInterval
Type: string (duration)
Required: Yes
Format: Go duration string (e.g., "30s", "5m", "1h")
How often to query the database and reconcile resources.
spec:
pollInterval: "1m" # Query every minuteExamples:
"30s"- Every 30 seconds"5m"- Every 5 minutes"1h"- Every hour"2h30m"- Every 2 hours and 30 minutes
spec.prune
Type: boolean
Required: No
Default: true
Whether to delete resources that were previously managed but no longer appear in query results.
spec:
prune: true # Delete stale resourcesWhen to disable:
- Resources should persist after database rows are removed
- External processes manage resource lifecycle
- Manual cleanup is preferred
spec.database
Type: object
Required: Yes
Database connection configuration.
spec.database.type
Type: string
Required: Yes
Allowed values: postgres
Type of database to connect to.
spec:
database:
type: postgresFuture support: MySQL, SQLite, other databases may be added.
spec.databaseConnectionSecretRef
Type: object
Required: Yes
Reference to a Kubernetes Secret containing database credentials.
connectionSecretRef.name
Type: string
Required: Yes
Name of the Secret containing connection details.
connectionSecretRef:
name: db-credentialsconnectionSecretRef.namespace
Type: string
Required: No
Default: DatabaseQueryResource's namespace
Namespace where the Secret is located.
connectionSecretRef:
name: db-credentials
namespace: database-secretsconnectionSecretRef.uriKey
Type: string
Required: No
Key in the Secret containing a complete PostgreSQL connection URI.
Format: postgresql://username:password@host:port/dbname?sslmode=...
connectionSecretRef:
name: my-postgres-cluster-app
uriKey: fqdn-uri # CloudNativePG fieldWhen provided: Takes precedence over individual field keys.
Use case: Perfect for CloudNativePG-generated secrets which include fqdn-uri.
hostKey
Type: string
Required: No
Default: "host"
Key in the Secret for the database hostname.
connectionSecretRef:
hostKey: DB_HOST # Secret key nameconnectionSecretRef.portKey
Type: string
Required: No
Default: "port"
Key in the Secret for the database port.
connectionSecretRef.userKey
Type: string
Required: No
Default: "username"
Key in the Secret for the database username.
passwordKey
Type: string
Required: No
Default: "password"
Key in the Secret for the database password.
connectionSecretRef.dbNameKey
Type: string
Required: No
Default: "dbname"
Key in the Secret for the database name.
connectionSecretRef.sslModeKey
Type: string
Required: No
Default: "sslmode"
Key in the Secret for the SSL mode.
SSL Modes: disable, require, verify-ca, verify-full, prefer
If the key is not found and not specified, defaults to "prefer".
Example Secret Structure:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
host: "postgres.database.svc.cluster.local"
port: "5432"
username: "myuser"
password: "mypassword"
dbname: "mydb"
sslmode: "require"spec.query
Type: string
Required: Yes
SQL query to execute against the database. Must return rows.
Supports:
- Standard SQL SELECT statements
- Apache AGE Cypher queries
- Multi-statement queries (setup commands + data query)
spec:
query: |
SELECT id, name, config
FROM resources
WHERE active = true;Multi-statement example (Apache AGE):
spec:
query: |
LOAD '$libdir/plugins/age';
SET search_path = ag_catalog, "$user", public;
SELECT * FROM cypher('graph', $$
MATCH (n:Node) RETURN n.id, n.name
$$) AS (id text, name text);How multi-statements work:
- All statements except the last are executed as a batch
- The final SELECT statement returns data
- Enables setup commands like
LOADandSET
spec.template
Type: string
Required: Yes
Go template for rendering Kubernetes resource manifests. Each database row is rendered once.
Template Context:
.Row- Map of column name → value for the current row.Metadata- Parent DatabaseQueryResource metadata
Functions: All Sprig functions are available.
spec:
template: |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Row.name | lower | replace "_" "-" }}
namespace: {{ .Metadata.Namespace }}
labels:
app: {{ .Row.app_name }}
environment: {{ .Row.env | default "dev" }}
data:
config: {{ .Row.config | toJson }}
created: {{ now | date "2006-01-02" }}Common patterns:
- String manipulation:
{{ .Row.name | lower | replace " " "-" }} - Conditionals:
{{ if eq .Row.env "prod" }}3{{ else }}1{{ end }} - Defaults:
{{ .Row.replicas | default 1 }} - JSON encoding:
{{ .Row.data | toJson }} - Base64:
{{ .Row.secret | b64enc }}
spec.statusUpdateQuery
Type: string
Required: No
Optional Go template for an SQL query that updates the database with resource status after reconciliation.
Template Context:
.Resource- The Kubernetes resource object (unstructured)
spec:
statusUpdateQueryTemplate: |
UPDATE deployments
SET
replicas = {{ .Resource.status.replicas | default 0 }},
ready_replicas = {{ .Resource.status.readyReplicas | default 0 }},
updated_at = NOW()
WHERE name = '{{ .Resource.metadata.name }}';Use cases:
- Sync Kubernetes resource status back to database
- Update timestamps or health checks
- Trigger database-side logic based on resource state
spec.changeDetection
Type: object
Required: No
Optional configuration for efficient change detection polling.
When enabled, the operator:
- Polls for changes every
changePollInterval(lightweight query) - Only runs full reconciliation when changes are detected
- Still runs full reconciliation at
pollIntervalas a safety net
changeDetection.enabled
Type: boolean
Required: Yes (if changeDetection is specified)
Enable or disable change detection.
changeDetection.tableName
Type: string
Required: Yes (if enabled)
Database table to monitor for changes. Can include schema: "schema.table".
changeDetection.timestampColumn
Type: string
Required: Yes (if enabled)
Column name that tracks when rows were last modified (e.g., "updated_at", "modified_at").
Requirements:
- Column type:
TIMESTAMPorTIMESTAMPTZ - Should be indexed for performance
- Auto-updated via trigger or application logic
changeDetection.changePollInterval
Type: string (duration)
Required: No
Default: "10s"
How often to check for changes. Should be shorter than spec.pollInterval.
Example:
spec:
pollInterval: "5m" # Full reconciliation every 5 minutes
changeDetection:
enabled: true
tableName: "public.resources"
timestampColumn: "updated_at"
changePollInterval: "10s" # Check for changes every 10 secondsDatabase setup:
-- Add timestamp column
ALTER TABLE resources ADD COLUMN updated_at TIMESTAMP DEFAULT NOW();
-- Create trigger to auto-update
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER resources_update_timestamp
BEFORE UPDATE ON resources
FOR EACH ROW
EXECUTE FUNCTION update_timestamp();
-- Index for performance
CREATE INDEX idx_resources_updated_at ON resources(updated_at);Validation
Before a DatabaseQueryResource is persisted, the operator's validating admission webhook
checks the parts of the spec that the CRD schema cannot express. An invalid resource is
rejected by the API server (for both CREATE and UPDATE) with an Invalid error naming the
offending field, instead of being accepted and failing later during reconciliation.
| Field | Rule |
|---|---|
spec.pollInterval | parseable as a Go duration (30s, 5m, 1h30m) and greater than zero |
spec.database.connectionSecretRef.name | non-empty |
spec.query | not empty and not whitespace only |
spec.template | not empty and parseable as a Go template with the operator's function set |
spec.statusUpdateQueryTemplate | parseable as a Go template when set |
spec.changeDetection.changePollInterval | parseable as a Go duration and greater than zero when set |
Rejected resources look like this:
Error from server (Invalid): error when creating "invalid.yaml": admission webhook
"vdatabasequeryresource-v1alpha1.konnektr.io" denied the request:
DatabaseQueryResource.konnektr.io "user-configmaps-example" is invalid:
spec.query: Invalid value: " ": query must not consist of whitespace onlyA changeDetection.changePollInterval that is not shorter than spec.pollInterval is
accepted with a warning, since change detection then cannot make reconciliation more
responsive.
The webhook serves on port 9443 behind the webhook-service Service and requires a TLS
certificate; the Helm chart and the config/ kustomize overlay both provision it with
cert-manager. Because the webhook is installed with failurePolicy: Fail, writes to
DatabaseQueryResource fail (rather than silently bypassing validation) while the webhook is
unavailable. See Installation for the cert-manager prerequisite.
Status Fields
The operator updates these status fields on the DatabaseQueryResource.
status.conditions
Array of condition objects indicating the current state.
Condition Types:
DBConnected- Database connection statusReconciled- Overall reconciliation status
Condition Structure:
status:
conditions:
- type: DBConnected
status: "True"
reason: Connected
message: "Successfully connected to the database"
lastTransitionTime: "2025-10-26T14:30:00Z"
- type: Reconciled
status: "True"
reason: Success
message: "Successfully queried DB and reconciled resources"
lastTransitionTime: "2025-10-26T14:30:05Z"status.lastPollTime
Timestamp of the last successful database query.
status:
lastPollTime: "2025-10-26T14:30:00Z"status.lastReconcileTime
Timestamp of the last successful full reconciliation.
status:
lastReconcileTime: "2025-10-26T14:30:05Z"status.managedResources
Array of identifiers for resources currently managed by this DBQR.
Format: <group>/<version>/<namespace>/<name> or <group>/<version>/<name> for cluster-scoped
status:
managedResources:
- v1/default/frontend-config
- v1/default/backend-config
- argoproj.io/v1alpha1/argocd/my-applicationstatus.lastChangeCheckTime
(Change detection only) Timestamp used in the last change detection query.
status:
lastChangeCheckTime: "2025-10-26T14:29:50Z"status.observedGeneration
Generation of the spec that was last processed.
status:
observedGeneration: 3Complete Example
apiVersion: konnektr.io/v1alpha1
kind: DatabaseQueryResource
metadata:
name: comprehensive-example
namespace: default
finalizers:
- konnektr.io/databasequeryresource-finalizer
spec:
# Reconciliation settings
pollInterval: "5m"
prune: true
# Change detection for fast updates
changeDetection:
enabled: true
tableName: "public.applications"
timestampColumn: "updated_at"
changePollInterval: "10s"
# Database connection
database:
type: postgres
connectionSecretRef:
name: postgres-cluster-app
namespace: database-ns
uriKey: fqdn-uri
# SQL query (with Apache AGE)
query: |
LOAD '$libdir/plugins/age';
SET search_path = ag_catalog, "$user", public;
SELECT * FROM cypher('apps', $$
MATCH (app:Application)
WHERE app.active = true
RETURN app.id AS id,
app.name AS name,
app.replicas AS replicas,
app.image AS image
$$) AS (id text, name text, replicas int, image text);
# Resource template
template: |
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Row.name | lower }}
namespace: {{ .Metadata.Namespace }}
labels:
app: {{ .Row.name }}
managed-by: {{ .Metadata.Name }}
spec:
replicas: {{ .Row.replicas | default 1 }}
selector:
matchLabels:
app: {{ .Row.name }}
template:
metadata:
labels:
app: {{ .Row.name }}
spec:
containers:
- name: app
image: {{ .Row.image }}
ports:
- containerPort: 8080
# Status update query
statusUpdateQueryTemplate: |
SELECT * FROM cypher('apps', $$
MATCH (app:Application {id: '{{ .Resource.metadata.labels.id }}'})
SET app.replicas = {{ .Resource.status.replicas | default 0 }},
app.ready_replicas = {{ .Resource.status.readyReplicas | default 0 }},
app.updated_at = timestamp()
RETURN app
$$) AS (app agtype);