Skip to content

Commit 44fd9bb

Browse files
authored
feat(catalog): accept extensions field in image catalogs (cloudnative-pg#10131)
Allow the CRD schema for ImageCatalog and ClusterImageCatalog to include the extensions field introduced in 1.29. The operator ignores the field since the Go struct simply skips unknown data during deserialization, but accepting it in the schema lets users share a single catalog manifest across clusters running different CNPG versions. To keep the CRD schema consistent across versions, relax the ExtensionConfiguration image field from required to optional, matching the 1.29 schema. A webhook validation ensures Cluster extensions still require an image reference on 1.28 and 1.27. Closes cloudnative-pg#10130 Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
1 parent 6a2f647 commit 44fd9bb

File tree

9 files changed

+198
-9
lines changed

9 files changed

+198
-9
lines changed

api/v1/cluster_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,8 +1477,8 @@ type ExtensionConfiguration struct {
14771477
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9_]*[a-z0-9])?$`
14781478
Name string `json:"name"`
14791479

1480-
// The image containing the extension, required
1481-
// +kubebuilder:validation:XValidation:rule="has(self.reference)",message="An image reference is required"
1480+
// The image containing the extension.
1481+
// +optional
14821482
ImageVolumeSource corev1.ImageVolumeSource `json:"image"`
14831483

14841484
// The list of directories inside the image which should be added to extension_control_path.

api/v1/imagecatalog_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ type CatalogImage struct {
3939
// +kubebuilder:validation:Minimum=10
4040
// The PostgreSQL major version of the image. Must be unique within the catalog.
4141
Major int `json:"major"`
42+
// The configuration of the extensions to be added
43+
// +optional
44+
// +listType=map
45+
// +listMapKey=name
46+
Extensions []ExtensionConfiguration `json:"extensions,omitempty"`
4247
}
4348

4449
// +genclient

api/v1/zz_generated.deepcopy.go

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/postgresql.cnpg.io_clusterimagecatalogs.yaml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,66 @@ spec:
5151
items:
5252
description: CatalogImage defines the image and major version
5353
properties:
54+
extensions:
55+
description: The configuration of the extensions to be added
56+
items:
57+
description: |-
58+
ExtensionConfiguration is the configuration used to add
59+
PostgreSQL extensions to the Cluster.
60+
properties:
61+
dynamic_library_path:
62+
description: |-
63+
The list of directories inside the image which should be added to dynamic_library_path.
64+
If not defined, defaults to "/lib".
65+
items:
66+
type: string
67+
type: array
68+
extension_control_path:
69+
description: |-
70+
The list of directories inside the image which should be added to extension_control_path.
71+
If not defined, defaults to "/share".
72+
items:
73+
type: string
74+
type: array
75+
image:
76+
description: The image containing the extension.
77+
properties:
78+
pullPolicy:
79+
description: |-
80+
Policy for pulling OCI objects. Possible values are:
81+
Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails.
82+
Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
83+
IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.
84+
Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
85+
type: string
86+
reference:
87+
description: |-
88+
Required: Image or artifact reference to be used.
89+
Behaves in the same way as pod.spec.containers[*].image.
90+
Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets.
91+
More info: https://kubernetes.io/docs/concepts/containers/images
92+
This field is optional to allow higher level config management to default or override
93+
container images in workload controllers like Deployments and StatefulSets.
94+
type: string
95+
type: object
96+
ld_library_path:
97+
description: The list of directories inside the image
98+
which should be added to ld_library_path.
99+
items:
100+
type: string
101+
type: array
102+
name:
103+
description: The name of the extension, required
104+
minLength: 1
105+
pattern: ^[a-z0-9]([-a-z0-9_]*[a-z0-9])?$
106+
type: string
107+
required:
108+
- name
109+
type: object
110+
type: array
111+
x-kubernetes-list-map-keys:
112+
- name
113+
x-kubernetes-list-type: map
54114
image:
55115
description: The image reference
56116
type: string

config/crd/bases/postgresql.cnpg.io_clusters.yaml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4382,7 +4382,7 @@ spec:
43824382
type: string
43834383
type: array
43844384
image:
4385-
description: The image containing the extension, required
4385+
description: The image containing the extension.
43864386
properties:
43874387
pullPolicy:
43884388
description: |-
@@ -4402,9 +4402,6 @@ spec:
44024402
container images in workload controllers like Deployments and StatefulSets.
44034403
type: string
44044404
type: object
4405-
x-kubernetes-validations:
4406-
- message: An image reference is required
4407-
rule: has(self.reference)
44084405
ld_library_path:
44094406
description: The list of directories inside the image which
44104407
should be added to ld_library_path.
@@ -4417,7 +4414,6 @@ spec:
44174414
pattern: ^[a-z0-9]([-a-z0-9_]*[a-z0-9])?$
44184415
type: string
44194416
required:
4420-
- image
44214417
- name
44224418
type: object
44234419
type: array

config/crd/bases/postgresql.cnpg.io_imagecatalogs.yaml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,66 @@ spec:
5050
items:
5151
description: CatalogImage defines the image and major version
5252
properties:
53+
extensions:
54+
description: The configuration of the extensions to be added
55+
items:
56+
description: |-
57+
ExtensionConfiguration is the configuration used to add
58+
PostgreSQL extensions to the Cluster.
59+
properties:
60+
dynamic_library_path:
61+
description: |-
62+
The list of directories inside the image which should be added to dynamic_library_path.
63+
If not defined, defaults to "/lib".
64+
items:
65+
type: string
66+
type: array
67+
extension_control_path:
68+
description: |-
69+
The list of directories inside the image which should be added to extension_control_path.
70+
If not defined, defaults to "/share".
71+
items:
72+
type: string
73+
type: array
74+
image:
75+
description: The image containing the extension.
76+
properties:
77+
pullPolicy:
78+
description: |-
79+
Policy for pulling OCI objects. Possible values are:
80+
Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails.
81+
Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
82+
IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.
83+
Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
84+
type: string
85+
reference:
86+
description: |-
87+
Required: Image or artifact reference to be used.
88+
Behaves in the same way as pod.spec.containers[*].image.
89+
Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets.
90+
More info: https://kubernetes.io/docs/concepts/containers/images
91+
This field is optional to allow higher level config management to default or override
92+
container images in workload controllers like Deployments and StatefulSets.
93+
type: string
94+
type: object
95+
ld_library_path:
96+
description: The list of directories inside the image
97+
which should be added to ld_library_path.
98+
items:
99+
type: string
100+
type: array
101+
name:
102+
description: The name of the extension, required
103+
minLength: 1
104+
pattern: ^[a-z0-9]([-a-z0-9_]*[a-z0-9])?$
105+
type: string
106+
required:
107+
- name
108+
type: object
109+
type: array
110+
x-kubernetes-list-map-keys:
111+
- name
112+
x-kubernetes-list-type: map
53113
image:
54114
description: The image reference
55115
type: string

docs/src/cloudnative-pg.v1.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ _Appears in:_
443443
| --- | --- | --- | --- | --- |
444444
| `image` _string_ | The image reference | True | | |
445445
| `major` _integer_ | The PostgreSQL major version of the image. Must be unique within the catalog. | True | | Minimum: 10 <br /> |
446+
| `extensions` _[ExtensionConfiguration](#extensionconfiguration) array_ | The configuration of the extensions to be added | | | |
446447

447448

448449
#### CertificatesConfiguration
@@ -978,12 +979,13 @@ PostgreSQL extensions to the Cluster.
978979

979980
_Appears in:_
980981

982+
- [CatalogImage](#catalogimage)
981983
- [PostgresConfiguration](#postgresconfiguration)
982984

983985
| Field | Description | Required | Default | Validation |
984986
| --- | --- | --- | --- | --- |
985987
| `name` _string_ | The name of the extension, required | True | | MinLength: 1 <br />Pattern: `^[a-z0-9]([-a-z0-9_]*[a-z0-9])?$` <br /> |
986-
| `image` _[ImageVolumeSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.34/#imagevolumesource-v1-core)_ | The image containing the extension, required | True | | |
988+
| `image` _[ImageVolumeSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.34/#imagevolumesource-v1-core)_ | The image containing the extension. | | | |
987989
| `extension_control_path` _string array_ | The list of directories inside the image which should be added to extension_control_path.<br />If not defined, defaults to "/share". | | | |
988990
| `dynamic_library_path` _string array_ | The list of directories inside the image which should be added to dynamic_library_path.<br />If not defined, defaults to "/lib". | | | |
989991
| `ld_library_path` _string array_ | The list of directories inside the image which should be added to ld_library_path. | | | |

internal/webhook/v1/cluster_webhook.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2850,6 +2850,16 @@ func (v *ClusterCustomValidator) validateExtensions(r *apiv1.Cluster) field.Erro
28502850
}
28512851
sanitizedVolumeNames.Put(sanitizedName)
28522852

2853+
if v.ImageVolumeSource.Reference == "" {
2854+
result = append(result,
2855+
field.Invalid(
2856+
basePath.Child("image", "reference"),
2857+
v.ImageVolumeSource.Reference,
2858+
fmt.Sprintf("Image reference for extension %q is required", v.Name),
2859+
),
2860+
)
2861+
}
2862+
28532863
controlPaths := stringset.New()
28542864
for j, path := range v.ExtensionControlPath {
28552865
if validateErr := ensureNotEmptyOrDuplicate(

internal/webhook/v1/cluster_webhook_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5772,6 +5772,53 @@ var _ = Describe("validateExtensions", func() {
57725772
Expect(err[0].Field).To(ContainSubstring("extensions[2].name"))
57735773
Expect(err[0].BadValue).To(Equal("pg-stat"))
57745774
})
5775+
5776+
It("returns an error when image reference is empty", func() {
5777+
cluster := &apiv1.Cluster{
5778+
Spec: apiv1.ClusterSpec{
5779+
PostgresConfiguration: apiv1.PostgresConfiguration{
5780+
Extensions: []apiv1.ExtensionConfiguration{
5781+
{
5782+
Name: "extone",
5783+
},
5784+
},
5785+
},
5786+
},
5787+
}
5788+
5789+
err := v.validateExtensions(cluster)
5790+
Expect(err).To(HaveLen(1))
5791+
Expect(err[0].Type).To(Equal(field.ErrorTypeInvalid))
5792+
Expect(err[0].Field).To(ContainSubstring("extensions[0].image.reference"))
5793+
})
5794+
5795+
It("returns errors for multiple extensions with empty image references", func() {
5796+
cluster := &apiv1.Cluster{
5797+
Spec: apiv1.ClusterSpec{
5798+
PostgresConfiguration: apiv1.PostgresConfiguration{
5799+
Extensions: []apiv1.ExtensionConfiguration{
5800+
{
5801+
Name: "extone",
5802+
},
5803+
{
5804+
Name: "exttwo",
5805+
ImageVolumeSource: corev1.ImageVolumeSource{
5806+
Reference: "exttwo:latest",
5807+
},
5808+
},
5809+
{
5810+
Name: "extthree",
5811+
},
5812+
},
5813+
},
5814+
},
5815+
}
5816+
5817+
err := v.validateExtensions(cluster)
5818+
Expect(err).To(HaveLen(2))
5819+
Expect(err[0].Field).To(ContainSubstring("extensions[0].image.reference"))
5820+
Expect(err[1].Field).To(ContainSubstring("extensions[2].image.reference"))
5821+
})
57755822
})
57765823

57775824
var _ = Describe("getInTreeBarmanWarnings", func() {

0 commit comments

Comments
 (0)