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
50 changes: 28 additions & 22 deletions config/crd/bases/postgres-operator.crunchydata.com_pgadmins.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -973,24 +973,27 @@ spec:
More info: https://www.pgadmin.org/docs/pgadmin4/latest/external_database.html
properties:
key:
description: The key of the secret to select from. Must be
a valid secret key.
description: Name of the data field within the Secret.
maxLength: 253
minLength: 1
pattern: ^[-._a-zA-Z0-9]+$
type: string
x-kubernetes-validations:
- message: cannot be "." or start with ".."
rule: self != "." && !self.startsWith("..")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Having a CEL rule felt like a bit much, but if we allow these values up-front, then we'll error trying to send them into volume source, secret ref, etc during reconcile.

name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
description: Name of the Secret.
maxLength: 253
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
optional:
description: Specify whether the Secret or its key must be
defined
description: Whether or not the Secret or its data must be
defined. Defaults to false.
type: boolean
required:
- key
- name
type: object
x-kubernetes-map-type: atomic
files:
Expand Down Expand Up @@ -1327,24 +1330,27 @@ spec:
More info: https://www.pgadmin.org/docs/pgadmin4/latest/ldap.html
properties:
key:
description: The key of the secret to select from. Must be
a valid secret key.
description: Name of the data field within the Secret.
maxLength: 253
minLength: 1
pattern: ^[-._a-zA-Z0-9]+$
type: string
x-kubernetes-validations:
- message: cannot be "." or start with ".."
rule: self != "." && !self.startsWith("..")
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
description: Name of the Secret.
maxLength: 253
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
optional:
description: Specify whether the Secret or its key must be
defined
description: Whether or not the Secret or its data must be
defined. Defaults to false.
type: boolean
required:
- key
- name
type: object
x-kubernetes-map-type: atomic
settings:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16895,24 +16895,27 @@ spec:
More info: https://www.pgadmin.org/docs/pgadmin4/latest/ldap.html
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
description: Name of the data field within the Secret.
maxLength: 253
minLength: 1
pattern: ^[-._a-zA-Z0-9]+$
type: string
x-kubernetes-validations:
- message: cannot be "." or start with ".."
rule: self != "." && !self.startsWith("..")
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
description: Name of the Secret.
maxLength: 253
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
optional:
description: Specify whether the Secret or its key
must be defined
description: Whether or not the Secret or its data
must be defined. Defaults to false.
type: boolean
required:
- key
- name
type: object
x-kubernetes-map-type: atomic
settings:
Expand Down
26 changes: 6 additions & 20 deletions internal/controller/standalone_pgadmin/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,9 @@ func podConfigFiles(configmap *corev1.ConfigMap, pgadmin v1beta1.PGAdmin) []core

if pgadmin.Spec.Config.ConfigDatabaseURI != nil {
config = append(config, corev1.VolumeProjection{
Secret: &corev1.SecretProjection{
LocalObjectReference: pgadmin.Spec.Config.ConfigDatabaseURI.LocalObjectReference,
Optional: pgadmin.Spec.Config.ConfigDatabaseURI.Optional,
Items: []corev1.KeyToPath{
{
Key: pgadmin.Spec.Config.ConfigDatabaseURI.Key,
Path: configDatabaseURIPath,
},
},
},
Secret: initialize.Pointer(
pgadmin.Spec.Config.ConfigDatabaseURI.AsProjection(configDatabaseURIPath),
),
})
}

Expand All @@ -252,16 +245,9 @@ func podConfigFiles(configmap *corev1.ConfigMap, pgadmin v1beta1.PGAdmin) []core
// - https://www.pgadmin.org/docs/pgadmin4/development/enabling_ldap_authentication.html
if pgadmin.Spec.Config.LDAPBindPassword != nil {
config = append(config, corev1.VolumeProjection{
Secret: &corev1.SecretProjection{
LocalObjectReference: pgadmin.Spec.Config.LDAPBindPassword.LocalObjectReference,
Optional: pgadmin.Spec.Config.LDAPBindPassword.Optional,
Items: []corev1.KeyToPath{
{
Key: pgadmin.Spec.Config.LDAPBindPassword.Key,
Path: ldapFilePath,
},
},
},
Secret: initialize.Pointer(
pgadmin.Spec.Config.LDAPBindPassword.AsProjection(ldapFilePath),
),
})
}

Expand Down
14 changes: 4 additions & 10 deletions internal/pgadmin/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

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

"github.com/crunchydata/postgres-operator/internal/initialize"
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

Expand Down Expand Up @@ -94,16 +95,9 @@ func podConfigFiles(configmap *corev1.ConfigMap, spec v1beta1.PGAdminPodSpec) []
// - https://www.pgadmin.org/docs/pgadmin4/development/enabling_ldap_authentication.html
if spec.Config.LDAPBindPassword != nil {
config = append(config, corev1.VolumeProjection{
Secret: &corev1.SecretProjection{
LocalObjectReference: spec.Config.LDAPBindPassword.LocalObjectReference,
Optional: spec.Config.LDAPBindPassword.Optional,
Items: []corev1.KeyToPath{
{
Key: spec.Config.LDAPBindPassword.Key,
Path: ldapPasswordPath,
},
},
},
Secret: initialize.Pointer(
spec.Config.LDAPBindPassword.AsProjection(ldapPasswordPath),
),
})
}

Expand Down
6 changes: 3 additions & 3 deletions internal/pgadmin/reconcile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,11 @@ volumes:
Name: "test",
}},
}}
cluster.Spec.UserInterface.PGAdmin.Config.LDAPBindPassword = &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
cluster.Spec.UserInterface.PGAdmin.Config.LDAPBindPassword = &v1beta1.OptionalSecretKeyRef{
SecretKeyRef: v1beta1.SecretKeyRef{
Name: "podtest",
Key: "podtestpw",
},
Key: "podtestpw",
}

call()
Expand Down
52 changes: 52 additions & 0 deletions pkg/apis/postgres-operator.crunchydata.com/v1beta1/config_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2021 - 2025 Crunchy Data Solutions, Inc.
//
// SPDX-License-Identifier: Apache-2.0

package v1beta1

import (
corev1 "k8s.io/api/core/v1"
)

// +structType=atomic
type OptionalSecretKeyRef struct {
SecretKeyRef `json:",inline"`

// Whether or not the Secret or its data must be defined. Defaults to false.
// +optional
Optional *bool `json:"optional,omitempty"`
}

// AsProjection returns a copy of this as a [corev1.SecretProjection].
func (in *OptionalSecretKeyRef) AsProjection(path string) corev1.SecretProjection {
out := in.SecretKeyRef.AsProjection(path)
if in.Optional != nil {
v := *in.Optional
out.Optional = &v
}
return out
}

// +structType=atomic
type SecretKeyRef struct {
// Name of the Secret.
// ---
// https://pkg.go.dev/k8s.io/kubernetes/pkg/apis/core/validation#ValidateSecretName
// +required
Name DNS1123Subdomain `json:"name"`

// Name of the data field within the Secret.
// ---
// https://releases.k8s.io/v1.32.0/pkg/apis/core/validation/validation.go#L2867
// https://pkg.go.dev/k8s.io/apimachinery/pkg/util/validation#IsConfigMapKey
// +required
Key ConfigDataKey `json:"key"`
}

// AsProjection returns a copy of this as a [corev1.SecretProjection].
func (in *SecretKeyRef) AsProjection(path string) corev1.SecretProjection {
var out corev1.SecretProjection
out.Name = in.Name
out.Items = []corev1.KeyToPath{{Key: in.Key, Path: path}}
return out
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2021 - 2025 Crunchy Data Solutions, Inc.
//
// SPDX-License-Identifier: Apache-2.0

package v1beta1_test

import (
"strings"
"testing"

"gotest.tools/v3/assert"
"sigs.k8s.io/yaml"

"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

func TestOptionalSecretKeyRefAsProjection(t *testing.T) {
t.Run("Null", func(t *testing.T) {
in := v1beta1.OptionalSecretKeyRef{}
in.Name, in.Key = "one", "two"

out := in.AsProjection("three")
b, err := yaml.Marshal(out)
assert.NilError(t, err)
assert.DeepEqual(t, string(b), strings.TrimSpace(`
items:
- key: two
path: three
name: one
`)+"\n")
})

t.Run("True", func(t *testing.T) {
True := true
in := v1beta1.OptionalSecretKeyRef{Optional: &True}
in.Name, in.Key = "one", "two"

out := in.AsProjection("three")
b, err := yaml.Marshal(out)
assert.NilError(t, err)
assert.DeepEqual(t, string(b), strings.TrimSpace(`
items:
- key: two
path: three
name: one
optional: true
`)+"\n")
})

t.Run("False", func(t *testing.T) {
False := false
in := v1beta1.OptionalSecretKeyRef{Optional: &False}
in.Name, in.Key = "one", "two"

out := in.AsProjection("three")
b, err := yaml.Marshal(out)
assert.NilError(t, err)
assert.DeepEqual(t, string(b), strings.TrimSpace(`
items:
- key: two
path: three
name: one
optional: false
`)+"\n")
})
}

func TestSecretKeyRefAsProjection(t *testing.T) {
in := v1beta1.SecretKeyRef{Name: "asdf", Key: "foobar"}
out := in.AsProjection("some-path")

b, err := yaml.Marshal(out)
assert.NilError(t, err)
assert.DeepEqual(t, string(b), strings.TrimSpace(`
items:
- key: foobar
path: some-path
name: asdf
`)+"\n")
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type PGAdminConfiguration struct {
// A Secret containing the value for the LDAP_BIND_PASSWORD setting.
// More info: https://www.pgadmin.org/docs/pgadmin4/latest/ldap.html
// +optional
LDAPBindPassword *corev1.SecretKeySelector `json:"ldapBindPassword,omitempty"`
LDAPBindPassword *OptionalSecretKeyRef `json:"ldapBindPassword,omitempty"`

// Settings for the pgAdmin server process. Keys should be uppercase and
// values must be constants.
Expand Down
19 changes: 19 additions & 0 deletions pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ import (
"k8s.io/kube-openapi/pkg/validation/strfmt"
)

// ---
// https://pkg.go.dev/k8s.io/apimachinery/pkg/util/validation#IsConfigMapKey
//
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^[-._a-zA-Z0-9]+$`
// +kubebuilder:validation:XValidation:rule=`self != "." && !self.startsWith("..")`,message=`cannot be "." or start with ".."`
type ConfigDataKey = string

// ---
// https://docs.k8s.io/concepts/overview/working-with-objects/names/#dns-subdomain-names
// https://pkg.go.dev/k8s.io/apimachinery/pkg/util/validation#IsDNS1123Subdomain
// https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Format
//
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
type DNS1123Subdomain = string

// ---
// Duration represents a string accepted by the Kubernetes API in the "duration"
// [format]. This format extends the "duration" [defined by OpenAPI] by allowing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type StandalonePGAdminConfiguration struct {
// A Secret containing the value for the CONFIG_DATABASE_URI setting.
// More info: https://www.pgadmin.org/docs/pgadmin4/latest/external_database.html
// +optional
ConfigDatabaseURI *corev1.SecretKeySelector `json:"configDatabaseURI,omitempty"`
ConfigDatabaseURI *OptionalSecretKeyRef `json:"configDatabaseURI,omitempty"`

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

// Settings for the pgAdmin server process. Keys should be uppercase and
// values must be constants.
Expand Down
Loading
Loading