Konnektr Logo

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 minute

Examples:

  • "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 resources

When 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: postgres

Future 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-credentials
connectionSecretRef.namespace

Type: string
Required: No
Default: DatabaseQueryResource's namespace

Namespace where the Secret is located.

connectionSecretRef:
  name: db-credentials
  namespace: database-secrets
connectionSecretRef.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 field

When 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 name
connectionSecretRef.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:

  1. All statements except the last are executed as a batch
  2. The final SELECT statement returns data
  3. Enables setup commands like LOAD and SET

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:

  1. Polls for changes every changePollInterval (lightweight query)
  2. Only runs full reconciliation when changes are detected
  3. Still runs full reconciliation at pollInterval as 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: TIMESTAMP or TIMESTAMPTZ
  • 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 seconds

Database 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.

FieldRule
spec.pollIntervalparseable as a Go duration (30s, 5m, 1h30m) and greater than zero
spec.database.connectionSecretRef.namenon-empty
spec.querynot empty and not whitespace only
spec.templatenot empty and parseable as a Go template with the operator's function set
spec.statusUpdateQueryTemplateparseable as a Go template when set
spec.changeDetection.changePollIntervalparseable 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 only

A 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 status
  • Reconciled - 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-application

status.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: 3

Complete 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);

Next Steps

On this page