Skip to content
Merged
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
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ indent_size = 2

[*.md]
max_line_length = off
indent_size = 2

[Makefile*]
indent_style = tab
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: Test

on:
push:
branches:
- main
pull_request:
types: [ opened, reopened, synchronize ]

Expand Down
319 changes: 188 additions & 131 deletions README.md

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions docs/cluster-connection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# ClusterConnection

The `ClusterConnection` Custom Resource Definition (CRD) defines the connection details for a PostgreSQL cluster.
It specifies the host, port, database, and the credentials to use for administrative operations.

Other Custom Resources (like `Database`, `Role`, `Schema`, `Grant`, `DefaultPrivilege`) reference a specific target PostgreSQL cluster using `clusterRef` on which to execute the operations.

## Spec

| Field | Type | Description | Required |
|------------------|---------------------|-----------------------------------------------------------------------|----------|
| `host` | `string` | The hostname of the PostgreSQL instance. | Yes |
| `port` | `integer` | The port of the PostgreSQL instance (1-65535). | Yes |
| `database` | `string` | The database to connect to (usually `postgres` for admin operations). | Yes |
| `adminSecretRef` | `SecretRef` | Reference to the secret containing admin credentials. | Yes |
| `parameters` | `map[string]string` | Additional connection parameters. | No |

### SecretRef

| Field | Type | Description | Required |
|-------------|----------|---------------------------------------------------------------------|----------|
| `name` | `string` | Name of the secret. | Yes |
| `namespace` | `string` | Namespace of the secret. If not specified, uses the CR's namespace. | No |

The referenced secret must be of type `kubernetes.io/basic-auth` and contain the keys `username` and `password`.

### Example

```yaml
apiVersion: v1
kind: Secret
metadata:
name: my-db-secret
type: kubernetes.io/basic-auth
stringData:
username: postgres
password: password
```

```yaml
apiVersion: postgresql.aboutbits.it/v1
kind: ClusterConnection
metadata:
name: my-postgres-connection
spec:
adminSecretRef:
name: my-db-secret
host: localhost
port: 5432
database: postgres
# Example parameters
parameters:
ApplicationName: "k8s-operator" # Helps identify this connection in Postgres logs
#sslmode: "require" # Enforce SSL encryption
#connectTimeout: "10" # Timeout in seconds for connection attempts
```
46 changes: 46 additions & 0 deletions docs/database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Database

The `Database` Custom Resource Definition (CRD) is responsible for managing PostgreSQL databases.

## Spec

| Field | Type | Description | Required | Immutable |
|-----------------|--------------------|------------------------------------------------------------------------------------------------------|----------|-----------|
| `clusterRef` | `ClusterReference` | Reference to the `ClusterConnection` to use. | Yes | No |
| `name` | `string` | The name of the database to create. | Yes | Yes |
| `owner` | `string` | The owner of the database. | No | No |
| `reclaimPolicy` | `string` | The policy for reclaiming the database when the CR is deleted. Values: `Retain` (Default), `Delete`. | No | No |

### ClusterReference

| Field | Type | Description | Required |
|-------------|----------|----------------------------------------------------------------------------------|----------|
| `name` | `string` | Name of the `ClusterConnection`. | Yes |
| `namespace` | `string` | Namespace of the `ClusterConnection`. If not specified, uses the CR's namespace. | No |

### Reclaim Policy

The `reclaimPolicy` controls what happens to the PostgreSQL database when the Custom Resource is deleted from Kubernetes.

- `Retain` (Default): The database remains in the PostgreSQL cluster. Only the Kubernetes Custom Resource is deleted. This prevents accidental data loss.
- `Delete`: The database is dropped from the PostgreSQL cluster. **Warning:** This will permanently delete the database and all its data.

## Example

```yaml
apiVersion: postgresql.aboutbits.it/v1
kind: Database
metadata:
name: my-database
spec:
clusterRef:
name: my-postgres-connection
name: my_database
owner: my_role
reclaimPolicy: Retain
```

## Official Documentation

- [CREATE DATABASE](https://www.postgresql.org/docs/current/sql-createdatabase.html)
- [ALTER DATABASE](https://www.postgresql.org/docs/current/sql-alterdatabase.html)
70 changes: 70 additions & 0 deletions docs/default-privilege.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# DefaultPrivilege

The `DefaultPrivilege` Custom Resource Definition (CRD) manages default privileges (ALTER DEFAULT PRIVILEGES) for objects created in the future.

## Spec

| Field | Type | Description | Required | Immutable |
|--------------|--------------------|---------------------------------------------------------------------------------------------------------|-------------|-----------|
| `clusterRef` | `ClusterReference` | Reference to the `ClusterConnection` to use. | Yes | No |
| `database` | `string` | The database where default privileges apply. | Yes | Yes |
| `role` | `string` | The role to which default privileges are granted. | Yes | Yes |
| `owner` | `string` | The role that owns the objects (the creator). Default privileges apply to objects created by this role. | Yes | Yes |
| `schema` | `string` | The schema where default privileges apply. Required, unless `objectType` is `schema`. | Conditional | Yes |
| `objectType` | `string` | The type of object. | Yes | Yes |
| `privileges` | `array[string]` | List of privileges to grant. | Yes | No |

### Object Types

Supported object types:

- `schema`
- `sequence`
- `table`

### Privileges

Supported privileges depend on the `objectType`:

- `connect`
- `create`
- `delete`
- `insert`
- `maintain`
- `references`
- `select`
- `temporary`
- `trigger`
- `truncate`
- `update`
- `usage`

### ClusterReference

| Field | Type | Description | Required |
|-------------|----------|----------------------------------------------------------------------------------|----------|
| `name` | `string` | Name of the `ClusterConnection`. | Yes |
| `namespace` | `string` | Namespace of the `ClusterConnection`. If not specified, uses the CR's namespace. | No |

## Example

```yaml
apiVersion: postgresql.aboutbits.it/v1
kind: DefaultPrivilege
metadata:
name: default-privileges-tables
spec:
clusterRef:
name: my-postgres-connection
database: my_database
role: read_only_role
owner: app_user
objectType: table
schema: public
privileges:
- select
```

## Official Documentation

- [ALTER DEFAULT PRIVILEGES](https://www.postgresql.org/docs/current/sql-alterdefaultprivileges.html)
114 changes: 114 additions & 0 deletions docs/docker-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Docker Environment

This example demonstrates how to set up a local development environment using Quarkus Dev Services to test the Operator manually.
As the K3s cluster port and the secrets change on every `./gradlew :operator:quarkusDev` run, you will have to manually update the port and secrets in the `~/.kube/config` every time.

## 1. Configure Kubeconfig from Dev Services

When running in dev mode (`make run` or via IntelliJ), Quarkus starts the pre-configured K3s and PostgreSQL Dev Services.

1. Access the Quarkus Dev UI at [http://localhost:8080/q/dev-ui/dev-services](http://localhost:8080/q/dev-ui/dev-services).
2. Locate the properties for the `kubernetes-client` Dev Service.
3. Convert these properties into a **Kubeconfig YAML** format, see the example below.
4. Merge this configuration into your local `~/.kube/config`. This allows your local environment to communicate with the ephemeral Kubernetes cluster provided by Dev Services.

```yml
apiVersion: v1
kind: Config
current-context: quarkus-cluster
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTmpjNU56UTFNREF3SGhjTk1qWXdNVEE1TVRZd01UUXdXaGNOTXpZd01UQTNNVFl3TVRRdwpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTmpjNU56UTFNREF3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFRYlpRQmgzdlNXMVExd1pST0tBQ1NlY3dreXhQUXVjVm9FN0tVM1MrQnYKZ1hJYzdCREQrb2JqTXFETXZuRkpNUlBCYUw0R2RDVVNsRDM3QzJUV01DNjlvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVURPdkI4eWt1VFJBTDRjRjhNOUo4Cit3STh5U2t3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnQWlZb0RsR2txUXd6WXVzcno5V3RMcUdEMXE2SmR6TVYKdW1nTFFPeFdNTEFDSVFDenQyMmxVTXJMNm1zMnBSRTBpQmZ3azNLbGdKSmJzZkp0YlI0bW9mRE16UT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: https://localhost:53658
name: quarkus-cluster
# ... more clusters
contexts:
- context:
cluster: quarkus-cluster
namespace: default
user: quarkus-user
name: quarkus-context
# ... more contexts
users:
- name: quarkus-user
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrakNDQVRlZ0F3SUJBZ0lJTlpCUDhySWZaTlV3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOelkzT1RjME5UQXdNQjRYRFRJMk1ERXdPVEUyTURFME1Gb1hEVEkzTURFdwpPVEUyTURFME1Gb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJIdnE1UWxVWVBFRldvYXEKRTJNRXI1cUM4TjBFMVkyRTJBTDcrYUl0a1YzYWZHRkkyMGtBODl1eEorc1phQ0ZzblJzYmE1ZmtuVEhPaGJtOApYZGpSeERxalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCUWJ3RktrRXhOaitCZjl2YVVNSGxtUi9oeC9PekFLQmdncWhrak9QUVFEQWdOSkFEQkcKQWlFQXlncll6eFIrZWoxWk5CdGdsZW5WT01HWWYrRWlPbkR6L1dzK1dQc1hnd0VDSVFEcEZhMlJ2RkRIVXhLMwpEODkzcGNLUkt1eU5MdXp6ZVZGODNlMURpckF1ZXc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCZGpDQ0FSMmdBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwClpXNTBMV05oUURFM05qYzVOelExTURBd0hoY05Nall3TVRBNU1UWXdNVFF3V2hjTk16WXdNVEEzTVRZd01UUXcKV2pBak1TRXdId1lEVlFRRERCaHJNM010WTJ4cFpXNTBMV05oUURFM05qYzVOelExTURBd1dUQVRCZ2NxaGtqTwpQUUlCQmdncWhrak9QUU1CQndOQ0FBVGNWU1NCOHdNakJIbHVOekVZb2krUUU1di9iUWl3cS81d2Jtb2hKU0FGCmZ2aE95eUhCcDBweDRRR0l6YU5BVm9kdkFIazRFV0ViMy9sMWpZVXNCSGlTbzBJd1FEQU9CZ05WSFE4QkFmOEUKQkFNQ0FxUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVHOEJTcEJNVFkvZ1gvYjJsREI1WgprZjRjZnpzd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ2FJemlrUzN6THNSakZTdit1Ny9BbmRNQzdDWTZaREF4Ckp4T1pKbzJVTmVRQ0lFaVlLQlpEVjhVZDZHN3BGU2doSVU5UVZYQ3FYSmx6MHNRbWpnRTJUeUZHCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFuN1dTOWJGZUhlaUpKMmJHcHFFTjBJc28vQzR3VEVNRFBSdENRNzNYMmhvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFZStybENWUmc4UVZhaHFvVFl3U3Ztb0x3M1FUVmpZVFlBdnY1b2kyUlhkcDhZVWpiU1FEegoyN0VuNnhsb0lXeWRHeHRybCtTZE1jNkZ1YnhkMk5IRU9nPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
# ... more users
```

## 2. Create PostgreSQL Connection and Secret

For the `postgresql` Dev Service, you can generate the necessary Custom Resources to test the Operator:

1. From the Dev UI, get the `postgresql` Dev Service properties (username, password, host, port).
2. Convert the `postgresql` Dev Service properties to a **Basic Auth Secret** and a **ClusterConnection** CR instance.
For more details see the [ClusterConnection](cluster-connection.md) CRD definition.
3. Apply the generated files using IntelliJ or `kubectl`.
![Apply Cluster Connection](images/apply-cluster-connection.png)

**Example Secret (`secret.yml`):**

```yaml
apiVersion: v1
kind: Secret
metadata:
name: quarkus-db-secret
labels:
app.kubernetes.io/name: quarkus-postgres
type: kubernetes.io/basic-auth
stringData:
# extracted from quarkus.datasource.username
username: root
# extracted from quarkus.datasource.password
password: password
```

**Example ClusterConnection (`cluster-connection.yml`):**

```yaml
apiVersion: postgresql.aboutbits.it/v1
kind: ClusterConnection
metadata:
name: quarkus-postgres-connection
spec:
adminSecretRef:
name: quarkus-db-secret
host: localhost
port: 5432
database: postgres
```

![Established Cluster Connection](images/established-cluster-connection.png)

## 3. Create a Role

Similarly, you can create a `Role` resource:

1. Manually create a **Role** CR instance.
For more details see the [Role](role.md) CRD definition.
2. Apply the file using IntelliJ or `kubectl`.

**Example Role (`role.yml`):**

```yaml
apiVersion: postgresql.aboutbits.it/v1
kind: Role
metadata:
name: test-role-from-crd
spec:
# The actual name of the role to be created in the PostgreSQL database
name: test-role-from-crd
comment: It simply works
# Connects this role definition to the specific Postgres ClusterConnection CR instance
clusterRef:
name: quarkus-postgres-connection
flags:
createdb: true
validUntil: "2026-12-31T23:59:59Z"
```

![Created Role](images/created-role.png)
![Role in pg_authid](images/role-in-table-pg-authid.png)

The same principle applies to other CRDs.
75 changes: 75 additions & 0 deletions docs/grant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Grant

The `Grant` Custom Resource Definition (CRD) is responsible for managing privileges (GRANT/REVOKE) on PostgreSQL objects.

## Spec

| Field | Type | Description | Required | Immutable |
|--------------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------|-------------|-----------|
| `clusterRef` | `ClusterReference` | Reference to the `ClusterConnection` to use. | Yes | No |
| `database` | `string` | The database containing the objects. | Yes | Yes |
| `role` | `string` | The role to which privileges are granted. | Yes | Yes |
| `schema` | `string` | The schema containing the objects. Required, unless `objectType` is `database`. | Conditional | Yes |
| `objectType` | `string` | The type of object. | Yes | Yes |
| `objects` | `array[string]` | List of object names. If empty, all objects of this `objectType` will be granted. Required, unless `objectType` is `database` or `schema`. | Conditional | No |
| `privileges` | `array[string]` | List of privileges to grant. | Yes | No |

### Object Types

Supported object types:

- `database`
- `schema`
- `sequence`
- `table`

### Privileges

Supported privileges depend on the `objectType`:

- `connect`
- `create`
- `delete`
- `insert`
- `maintain`
- `references`
- `select`
- `temporary`
- `trigger`
- `truncate`
- `update`
- `usage`

### ClusterReference

| Field | Type | Description | Required |
|-------------|----------|----------------------------------------------------------------------------------|----------|
| `name` | `string` | Name of the `ClusterConnection`. | Yes |
| `namespace` | `string` | Namespace of the `ClusterConnection`. If not specified, uses the CR's namespace. | No |

## Example

```yaml
apiVersion: postgresql.aboutbits.it/v1
kind: Grant
metadata:
name: grant-select-tables
spec:
clusterRef:
name: my-postgres-connection
database: my_database
role: my_role
objectType: table
schema: public
objects:
- my_table
- another_table
privileges:
- select
- insert
```

## Official Documentation

- [GRANT](https://www.postgresql.org/docs/current/sql-grant.html)
- [REVOKE](https://www.postgresql.org/docs/current/sql-revoke.html)
File renamed without changes
Loading