Skip to content

Commit 6248b1e

Browse files
authored
Add a Terraform pitfalls section to the docs (fixes #58) (#59)
1 parent 072e1c3 commit 6248b1e

2 files changed

Lines changed: 107 additions & 0 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ Further documentation of each Custom Resource can be found here:
6464
- [Grant](docs/grant.md) - Manage privileges.
6565
- [DefaultPrivilege](docs/default-privilege.md) - Manage default privileges.
6666

67+
If you manage the Custom Resources with Terraform, please also read:
68+
69+
- [Terraform](docs/terraform.md) - Pitfalls when using the `kubernetes_manifest` resource.
70+
6771
### Declarative Management
6872

6973
The Operator leverages the power of Kubernetes Custom Resource Definitions (CRDs) to manage PostgreSQL resources declaratively.

docs/terraform.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Terraform
2+
3+
The Custom Resources of this Operator can be managed with the
4+
[`kubernetes_manifest`](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest)
5+
resource of the `hashicorp/kubernetes` provider.
6+
7+
## Never set optional fields to `null`
8+
9+
**Omit** optional spec fields from the manifest instead of setting them to `null`. A field that is present but `null` produces a permanent in-place update on every single plan:
10+
11+
```hcl
12+
# module.postgresql_role.kubernetes_manifest.postgresql_role_cr will be updated in-place
13+
~ resource "kubernetes_manifest" "postgresql_role_cr" {
14+
~ object = {
15+
~ spec = {
16+
+ comment = (known after apply)
17+
~ flags = {
18+
+ validUntil = (known after apply)
19+
}
20+
}
21+
}
22+
}
23+
```
24+
25+
### Why this happens
26+
27+
While planning, the provider fills every field with the CRD schema that the configuration does not set with an unknown value,
28+
and then takes the value from the prior state again - unless the field was present in the previous configuration.
29+
In that case it keeps the value unknown, to give the API server a chance to default it.
30+
31+
A `null` counts as "present" here. As this Operator does not default these fields, the applied object never contains them, the refreshed state holds `null` again,
32+
and the next plan repeats the same `(known after apply)`. The plan never converges.
33+
34+
A field that is **absent** from the configuration produces no diff at all.
35+
36+
This is a known limitation of the provider, see [hashicorp/terraform-provider-kubernetes#2669](https://github.com/hashicorp/terraform-provider-kubernetes/issues/2669).
37+
38+
### How to avoid it
39+
40+
Build the `spec` with [`merge`](https://developer.hashicorp.com/terraform/language/functions/merge)
41+
and only add optional attributes when they actually have a value:
42+
43+
```hcl
44+
variable "comment" {
45+
type = string
46+
default = null
47+
nullable = true
48+
}
49+
50+
variable "valid_until" {
51+
type = string
52+
default = null
53+
nullable = true
54+
}
55+
56+
resource "kubernetes_manifest" "postgresql_role_cr" {
57+
manifest = {
58+
apiVersion = "postgresql.aboutbits.it/v1"
59+
kind = "Role"
60+
61+
metadata = {
62+
namespace = var.namespace
63+
name = var.name
64+
}
65+
66+
spec = merge(
67+
{
68+
clusterRef = {
69+
namespace = var.cluster_ref_namespace
70+
name = var.cluster_ref_name
71+
}
72+
name = var.role
73+
flags = merge(
74+
{
75+
createdb = var.flag_createdb
76+
},
77+
var.valid_until == null ? {} : {
78+
validUntil = var.valid_until
79+
},
80+
)
81+
},
82+
var.comment == null ? {} : {
83+
comment = var.comment
84+
},
85+
)
86+
}
87+
}
88+
```
89+
90+
For variables that have a non-`null` default, declaring them as `nullable = false` additionally makes Terraform fall back to the default whenever a caller passes `null` explicitly.
91+
92+
### Affected fields
93+
94+
Every optional field of every Custom Resource is affected, in particular:
95+
96+
| Custom Resource | Optional fields |
97+
|---------------------|------------------------------------------------------------------------------------------------|
98+
| `ClusterConnection` | `parameters`, `adminSecretRef.namespace` |
99+
| `Database` | `owner`, `reclaimPolicy`, `clusterRef.namespace` |
100+
| `Schema` | `owner`, `reclaimPolicy`, `clusterRef.namespace` |
101+
| `Role` | `comment`, `passwordSecretRef`, `flags` (including `flags.validUntil`), `clusterRef.namespace` |
102+
| `Grant` | `schema`, `objects`, `clusterRef.namespace` |
103+
| `DefaultPrivilege` | `schema`, `clusterRef.namespace` |

0 commit comments

Comments
 (0)