Skip to content

Commit 85636a8

Browse files
committed
Add an API struct representing a single Secret value
This adds validation to the recurring pattern of selecting a single value from a Secret. Note that the "name" field is now required. Secrets are best mounted as files, and the logic for translating these references into volume projections is now consolidated in two exported methods.
1 parent 8b87822 commit 85636a8

File tree

11 files changed

+248
-72
lines changed

11 files changed

+248
-72
lines changed

config/crd/bases/postgres-operator.crunchydata.com_pgadmins.yaml

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -973,24 +973,27 @@ spec:
973973
More info: https://www.pgadmin.org/docs/pgadmin4/latest/external_database.html
974974
properties:
975975
key:
976-
description: The key of the secret to select from. Must be
977-
a valid secret key.
976+
description: Name of the data field within the Secret.
977+
maxLength: 253
978+
minLength: 1
979+
pattern: ^[-._a-zA-Z0-9]+$
978980
type: string
981+
x-kubernetes-validations:
982+
- message: cannot be "." or start with ".."
983+
rule: self != "." && !self.startsWith("..")
979984
name:
980-
default: ""
981-
description: |-
982-
Name of the referent.
983-
This field is effectively required, but due to backwards compatibility is
984-
allowed to be empty. Instances of this type with an empty value here are
985-
almost certainly wrong.
986-
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
985+
description: Name of the Secret.
986+
maxLength: 253
987+
minLength: 1
988+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
987989
type: string
988990
optional:
989-
description: Specify whether the Secret or its key must be
990-
defined
991+
description: Whether or not the Secret or its data must be
992+
defined. Defaults to false.
991993
type: boolean
992994
required:
993995
- key
996+
- name
994997
type: object
995998
x-kubernetes-map-type: atomic
996999
files:
@@ -1327,24 +1330,27 @@ spec:
13271330
More info: https://www.pgadmin.org/docs/pgadmin4/latest/ldap.html
13281331
properties:
13291332
key:
1330-
description: The key of the secret to select from. Must be
1331-
a valid secret key.
1333+
description: Name of the data field within the Secret.
1334+
maxLength: 253
1335+
minLength: 1
1336+
pattern: ^[-._a-zA-Z0-9]+$
13321337
type: string
1338+
x-kubernetes-validations:
1339+
- message: cannot be "." or start with ".."
1340+
rule: self != "." && !self.startsWith("..")
13331341
name:
1334-
default: ""
1335-
description: |-
1336-
Name of the referent.
1337-
This field is effectively required, but due to backwards compatibility is
1338-
allowed to be empty. Instances of this type with an empty value here are
1339-
almost certainly wrong.
1340-
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
1342+
description: Name of the Secret.
1343+
maxLength: 253
1344+
minLength: 1
1345+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
13411346
type: string
13421347
optional:
1343-
description: Specify whether the Secret or its key must be
1344-
defined
1348+
description: Whether or not the Secret or its data must be
1349+
defined. Defaults to false.
13451350
type: boolean
13461351
required:
13471352
- key
1353+
- name
13481354
type: object
13491355
x-kubernetes-map-type: atomic
13501356
settings:

config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16895,24 +16895,27 @@ spec:
1689516895
More info: https://www.pgadmin.org/docs/pgadmin4/latest/ldap.html
1689616896
properties:
1689716897
key:
16898-
description: The key of the secret to select from. Must
16899-
be a valid secret key.
16898+
description: Name of the data field within the Secret.
16899+
maxLength: 253
16900+
minLength: 1
16901+
pattern: ^[-._a-zA-Z0-9]+$
1690016902
type: string
16903+
x-kubernetes-validations:
16904+
- message: cannot be "." or start with ".."
16905+
rule: self != "." && !self.startsWith("..")
1690116906
name:
16902-
default: ""
16903-
description: |-
16904-
Name of the referent.
16905-
This field is effectively required, but due to backwards compatibility is
16906-
allowed to be empty. Instances of this type with an empty value here are
16907-
almost certainly wrong.
16908-
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
16907+
description: Name of the Secret.
16908+
maxLength: 253
16909+
minLength: 1
16910+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
1690916911
type: string
1691016912
optional:
16911-
description: Specify whether the Secret or its key
16912-
must be defined
16913+
description: Whether or not the Secret or its data
16914+
must be defined. Defaults to false.
1691316915
type: boolean
1691416916
required:
1691516917
- key
16918+
- name
1691616919
type: object
1691716920
x-kubernetes-map-type: atomic
1691816921
settings:

internal/controller/standalone_pgadmin/pod.go

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -229,16 +229,9 @@ func podConfigFiles(configmap *corev1.ConfigMap, pgadmin v1beta1.PGAdmin) []core
229229

230230
if pgadmin.Spec.Config.ConfigDatabaseURI != nil {
231231
config = append(config, corev1.VolumeProjection{
232-
Secret: &corev1.SecretProjection{
233-
LocalObjectReference: pgadmin.Spec.Config.ConfigDatabaseURI.LocalObjectReference,
234-
Optional: pgadmin.Spec.Config.ConfigDatabaseURI.Optional,
235-
Items: []corev1.KeyToPath{
236-
{
237-
Key: pgadmin.Spec.Config.ConfigDatabaseURI.Key,
238-
Path: configDatabaseURIPath,
239-
},
240-
},
241-
},
232+
Secret: initialize.Pointer(
233+
pgadmin.Spec.Config.ConfigDatabaseURI.AsProjection(configDatabaseURIPath),
234+
),
242235
})
243236
}
244237

@@ -252,16 +245,9 @@ func podConfigFiles(configmap *corev1.ConfigMap, pgadmin v1beta1.PGAdmin) []core
252245
// - https://www.pgadmin.org/docs/pgadmin4/development/enabling_ldap_authentication.html
253246
if pgadmin.Spec.Config.LDAPBindPassword != nil {
254247
config = append(config, corev1.VolumeProjection{
255-
Secret: &corev1.SecretProjection{
256-
LocalObjectReference: pgadmin.Spec.Config.LDAPBindPassword.LocalObjectReference,
257-
Optional: pgadmin.Spec.Config.LDAPBindPassword.Optional,
258-
Items: []corev1.KeyToPath{
259-
{
260-
Key: pgadmin.Spec.Config.LDAPBindPassword.Key,
261-
Path: ldapFilePath,
262-
},
263-
},
264-
},
248+
Secret: initialize.Pointer(
249+
pgadmin.Spec.Config.LDAPBindPassword.AsProjection(ldapFilePath),
250+
),
265251
})
266252
}
267253

internal/pgadmin/config.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
corev1 "k8s.io/api/core/v1"
1111

12+
"github.com/crunchydata/postgres-operator/internal/initialize"
1213
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
1314
)
1415

@@ -94,16 +95,9 @@ func podConfigFiles(configmap *corev1.ConfigMap, spec v1beta1.PGAdminPodSpec) []
9495
// - https://www.pgadmin.org/docs/pgadmin4/development/enabling_ldap_authentication.html
9596
if spec.Config.LDAPBindPassword != nil {
9697
config = append(config, corev1.VolumeProjection{
97-
Secret: &corev1.SecretProjection{
98-
LocalObjectReference: spec.Config.LDAPBindPassword.LocalObjectReference,
99-
Optional: spec.Config.LDAPBindPassword.Optional,
100-
Items: []corev1.KeyToPath{
101-
{
102-
Key: spec.Config.LDAPBindPassword.Key,
103-
Path: ldapPasswordPath,
104-
},
105-
},
106-
},
98+
Secret: initialize.Pointer(
99+
spec.Config.LDAPBindPassword.AsProjection(ldapPasswordPath),
100+
),
107101
})
108102
}
109103

internal/pgadmin/reconcile_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,11 @@ volumes:
316316
Name: "test",
317317
}},
318318
}}
319-
cluster.Spec.UserInterface.PGAdmin.Config.LDAPBindPassword = &corev1.SecretKeySelector{
320-
LocalObjectReference: corev1.LocalObjectReference{
319+
cluster.Spec.UserInterface.PGAdmin.Config.LDAPBindPassword = &v1beta1.OptionalSecretKeyRef{
320+
SecretKeyRef: v1beta1.SecretKeyRef{
321321
Name: "podtest",
322+
Key: "podtestpw",
322323
},
323-
Key: "podtestpw",
324324
}
325325

326326
call()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2021 - 2025 Crunchy Data Solutions, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package v1beta1
6+
7+
import (
8+
corev1 "k8s.io/api/core/v1"
9+
)
10+
11+
// +structType=atomic
12+
type OptionalSecretKeyRef struct {
13+
SecretKeyRef `json:",inline"`
14+
15+
// Whether or not the Secret or its data must be defined. Defaults to false.
16+
// +optional
17+
Optional *bool `json:"optional,omitempty"`
18+
}
19+
20+
// AsProjection returns a copy of this as a [corev1.SecretProjection].
21+
func (in *OptionalSecretKeyRef) AsProjection(path string) corev1.SecretProjection {
22+
out := in.SecretKeyRef.AsProjection(path)
23+
if in.Optional != nil {
24+
v := *in.Optional
25+
out.Optional = &v
26+
}
27+
return out
28+
}
29+
30+
// +structType=atomic
31+
type SecretKeyRef struct {
32+
// Name of the Secret.
33+
// ---
34+
// https://pkg.go.dev/k8s.io/kubernetes/pkg/apis/core/validation#ValidateSecretName
35+
// +required
36+
Name DNS1123Subdomain `json:"name"`
37+
38+
// Name of the data field within the Secret.
39+
// ---
40+
// https://releases.k8s.io/v1.32.0/pkg/apis/core/validation/validation.go#L2867
41+
// https://pkg.go.dev/k8s.io/apimachinery/pkg/util/validation#IsConfigMapKey
42+
// +required
43+
Key ConfigDataKey `json:"key"`
44+
}
45+
46+
// AsProjection returns a copy of this as a [corev1.SecretProjection].
47+
func (in *SecretKeyRef) AsProjection(path string) corev1.SecretProjection {
48+
var out corev1.SecretProjection
49+
out.Name = in.Name
50+
out.Items = []corev1.KeyToPath{{Key: in.Key, Path: path}}
51+
return out
52+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2021 - 2025 Crunchy Data Solutions, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package v1beta1_test
6+
7+
import (
8+
"strings"
9+
"testing"
10+
11+
"gotest.tools/v3/assert"
12+
"sigs.k8s.io/yaml"
13+
14+
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
15+
)
16+
17+
func TestOptionalSecretKeyRefAsProjection(t *testing.T) {
18+
t.Run("Null", func(t *testing.T) {
19+
in := v1beta1.OptionalSecretKeyRef{}
20+
in.Name, in.Key = "one", "two"
21+
22+
out := in.AsProjection("three")
23+
b, err := yaml.Marshal(out)
24+
assert.NilError(t, err)
25+
assert.DeepEqual(t, string(b), strings.TrimSpace(`
26+
items:
27+
- key: two
28+
path: three
29+
name: one
30+
`)+"\n")
31+
})
32+
33+
t.Run("True", func(t *testing.T) {
34+
True := true
35+
in := v1beta1.OptionalSecretKeyRef{Optional: &True}
36+
in.Name, in.Key = "one", "two"
37+
38+
out := in.AsProjection("three")
39+
b, err := yaml.Marshal(out)
40+
assert.NilError(t, err)
41+
assert.DeepEqual(t, string(b), strings.TrimSpace(`
42+
items:
43+
- key: two
44+
path: three
45+
name: one
46+
optional: true
47+
`)+"\n")
48+
})
49+
50+
t.Run("False", func(t *testing.T) {
51+
False := false
52+
in := v1beta1.OptionalSecretKeyRef{Optional: &False}
53+
in.Name, in.Key = "one", "two"
54+
55+
out := in.AsProjection("three")
56+
b, err := yaml.Marshal(out)
57+
assert.NilError(t, err)
58+
assert.DeepEqual(t, string(b), strings.TrimSpace(`
59+
items:
60+
- key: two
61+
path: three
62+
name: one
63+
optional: false
64+
`)+"\n")
65+
})
66+
}
67+
68+
func TestSecretKeyRefAsProjection(t *testing.T) {
69+
in := v1beta1.SecretKeyRef{Name: "asdf", Key: "foobar"}
70+
out := in.AsProjection("some-path")
71+
72+
b, err := yaml.Marshal(out)
73+
assert.NilError(t, err)
74+
assert.DeepEqual(t, string(b), strings.TrimSpace(`
75+
items:
76+
- key: foobar
77+
path: some-path
78+
name: asdf
79+
`)+"\n")
80+
}

pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgadmin_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type PGAdminConfiguration struct {
1717
// A Secret containing the value for the LDAP_BIND_PASSWORD setting.
1818
// More info: https://www.pgadmin.org/docs/pgadmin4/latest/ldap.html
1919
// +optional
20-
LDAPBindPassword *corev1.SecretKeySelector `json:"ldapBindPassword,omitempty"`
20+
LDAPBindPassword *OptionalSecretKeyRef `json:"ldapBindPassword,omitempty"`
2121

2222
// Settings for the pgAdmin server process. Keys should be uppercase and
2323
// values must be constants.

pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,25 @@ import (
1313
"k8s.io/kube-openapi/pkg/validation/strfmt"
1414
)
1515

16+
// ---
17+
// https://pkg.go.dev/k8s.io/apimachinery/pkg/util/validation#IsConfigMapKey
18+
//
19+
// +kubebuilder:validation:MinLength=1
20+
// +kubebuilder:validation:MaxLength=253
21+
// +kubebuilder:validation:Pattern=`^[-._a-zA-Z0-9]+$`
22+
// +kubebuilder:validation:XValidation:rule=`self != "." && !self.startsWith("..")`,message=`cannot be "." or start with ".."`
23+
type ConfigDataKey = string
24+
25+
// ---
26+
// https://docs.k8s.io/concepts/overview/working-with-objects/names/#dns-subdomain-names
27+
// https://pkg.go.dev/k8s.io/apimachinery/pkg/util/validation#IsDNS1123Subdomain
28+
// https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Format
29+
//
30+
// +kubebuilder:validation:MinLength=1
31+
// +kubebuilder:validation:MaxLength=253
32+
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
33+
type DNS1123Subdomain = string
34+
1635
// ---
1736
// Duration represents a string accepted by the Kubernetes API in the "duration"
1837
// [format]. This format extends the "duration" [defined by OpenAPI] by allowing

pkg/apis/postgres-operator.crunchydata.com/v1beta1/standalone_pgadmin_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type StandalonePGAdminConfiguration struct {
1919
// A Secret containing the value for the CONFIG_DATABASE_URI setting.
2020
// More info: https://www.pgadmin.org/docs/pgadmin4/latest/external_database.html
2121
// +optional
22-
ConfigDatabaseURI *corev1.SecretKeySelector `json:"configDatabaseURI,omitempty"`
22+
ConfigDatabaseURI *OptionalSecretKeyRef `json:"configDatabaseURI,omitempty"`
2323

2424
// Settings for the gunicorn server.
2525
// More info: https://docs.gunicorn.org/en/latest/settings.html
@@ -32,7 +32,7 @@ type StandalonePGAdminConfiguration struct {
3232
// A Secret containing the value for the LDAP_BIND_PASSWORD setting.
3333
// More info: https://www.pgadmin.org/docs/pgadmin4/latest/ldap.html
3434
// +optional
35-
LDAPBindPassword *corev1.SecretKeySelector `json:"ldapBindPassword,omitempty"`
35+
LDAPBindPassword *OptionalSecretKeyRef `json:"ldapBindPassword,omitempty"`
3636

3737
// Settings for the pgAdmin server process. Keys should be uppercase and
3838
// values must be constants.

0 commit comments

Comments
 (0)