Skip to content

Commit

Permalink
Add AppBinding PostgresRef in FerretDB API (#1239)
Browse files Browse the repository at this point in the history
Signed-off-by: sayedppqq <sayed@appscode.com>
  • Loading branch information
sayedppqq authored Jun 28, 2024
1 parent b88f519 commit b4f0c7a
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 205 deletions.
5 changes: 4 additions & 1 deletion apis/kubedb/v1alpha2/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -1223,11 +1223,14 @@ const (

FerretDBServerPath = "/etc/certs/server"

FerretDBExternalClientPath = "/etc/certs/ext"

FerretDBDefaultPort = 27017
FerretDBMetricsPort = 8080
FerretDBTLSPort = 27018

FerretDBMetricsPath = "/debug/metrics"
FerretDBMetricsPath = "/debug/metrics"
FerretDBMetricsPortName = "metrics"
)

// =========================== ClickHouse Constants ============================
Expand Down
62 changes: 52 additions & 10 deletions apis/kubedb/v1alpha2/ferretdb_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package v1alpha2
import (
"context"
"fmt"
"net/url"

"kubedb.dev/apimachinery/apis"
catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1"
Expand Down Expand Up @@ -110,7 +111,15 @@ func (f *FerretDB) GetAuthSecretName() string {
if f.Spec.AuthSecret != nil && f.Spec.AuthSecret.Name != "" {
return f.Spec.AuthSecret.Name
}
return meta_util.NameWithSuffix(f.PgBackendName(), "auth")
return meta_util.NameWithSuffix(f.OffshootName(), "auth")
}

func (f *FerretDB) GetPersistentSecrets() []string {
var secrets []string
if f.Spec.AuthSecret != nil {
secrets = append(secrets, f.Spec.AuthSecret.Name)
}
return secrets
}

// AsOwner returns owner reference to resources
Expand Down Expand Up @@ -143,6 +152,14 @@ func (f *FerretDB) GetCertSecretName(alias FerretDBCertificateAlias) string {
return f.CertificateName(alias)
}

func (f *FerretDB) GetExternalBackendClientSecretName() string {
return f.Name + "-ext-pg-client-cert"
}

func (f *FerretDB) GetSecretVolumeName(secretName string) string {
return secretName + "-vol"
}

func (f *FerretDB) SetHealthCheckerDefaults() {
if f.Spec.HealthChecker.PeriodSeconds == nil {
f.Spec.HealthChecker.PeriodSeconds = pointer.Int32P(10)
Expand Down Expand Up @@ -210,11 +227,13 @@ func (f *FerretDB) SetDefaults() {
f.Spec.Backend.LinkedDB = "ferretdb"
}
}

if f.Spec.AuthSecret == nil {
f.Spec.AuthSecret = &SecretReference{
ExternallyManaged: f.Spec.Backend.ExternallyManaged,
ExternallyManaged: false,
}
}

f.Spec.Monitor.SetDefaults()
if f.Spec.Monitor != nil && f.Spec.Monitor.Prometheus != nil {
if f.Spec.Monitor.Prometheus.Exporter.SecurityContext.RunAsUser == nil {
Expand All @@ -227,14 +246,8 @@ func (f *FerretDB) SetDefaults() {

defaultVersion := "13.13"
if !f.Spec.Backend.ExternallyManaged {
if f.Spec.Backend.Postgres == nil {
f.Spec.Backend.Postgres = &PostgresRef{
Version: &defaultVersion,
}
} else {
if f.Spec.Backend.Postgres.Version == nil {
f.Spec.Backend.Postgres.Version = &defaultVersion
}
if f.Spec.Backend.Version == nil {
f.Spec.Backend.Version = &defaultVersion
}
}
f.SetTLSDefaults()
Expand Down Expand Up @@ -370,3 +383,32 @@ func (f *FerretDB) ReplicasAreReady(lister pslister.PetSetLister) (bool, string,
expectedItems := 1
return checkReplicasOfPetSet(lister.PetSets(f.Namespace), labels.SelectorFromSet(f.OffshootLabels()), expectedItems)
}

func (f *FerretDB) GetSSLModeFromAppBinding(apb *appcat.AppBinding) (PostgresSSLMode, error) {
var sslMode string
if apb.Spec.ClientConfig.URL != nil {
parsedURL, err := url.Parse(*apb.Spec.ClientConfig.URL)
if err != nil {
return "", fmt.Errorf("parse error in appbinding %s/%s 'spec.clientConfig.url'. error: %v", apb.Namespace, apb.Name, err)
}
if parsedURL.Scheme != "postgres" && parsedURL.Scheme != "postgresql" {
return "", fmt.Errorf("invalid scheme provided in URL in provided appbinding %s/%s", apb.Namespace, apb.Name)
}
sslMode = parsedURL.Query().Get("sslmode")
}
if apb.Spec.ClientConfig.Service != nil {
values, err := url.ParseQuery(apb.Spec.ClientConfig.Service.Query)
if err != nil {
return "", fmt.Errorf("parse error in appbinding %s/%s 'spec.clientConfig.service.query'. error: %v", apb.Namespace, apb.Name, err)
}
if sslMode != "" && sslMode != values.Get("sslmode") {
return "", fmt.Errorf("sslMode is not same in 'spec.clientConfig.service.query' and 'spec.clientConfig.url' of appbinding %s/%s", apb.Namespace, apb.Name)
}
sslMode = values.Get("sslmode")
}
// If sslMode is not specified anywhere, it will be disabled
if sslMode == "" {
sslMode = "disable"
}
return PostgresSSLMode(sslMode), nil
}
33 changes: 7 additions & 26 deletions apis/kubedb/v1alpha2/ferretdb_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ type FerretDBSpec struct {
Replicas *int32 `json:"replicas,omitempty"`

// Database authentication secret.
// If authSecret is nil, authSecret.externallyManaged will set to backend.externallyManaged
// Use this only when backend is internally managed.
// For externally managed backend, we will get the authSecret from AppBinding
// +optional
AuthSecret *SecretReference `json:"authSecret,omitempty"`

Expand Down Expand Up @@ -121,38 +122,18 @@ type FerretDBStatus struct {
}

type FerretDBBackend struct {
// PostgresRef refers to the AppBinding of the backend Postgres server
// +optional
Postgres *PostgresRef `json:"postgres,omitempty"`
PostgresRef *kmapi.ObjectReference `json:"postgresRef,omitempty"`
// Which versions pg will be used as backend of ferretdb. default 13.13 when backend internally managed
// +optional
Version *string `json:"version,omitempty"`
// A DB inside backend specifically made for ferretdb
// +optional
LinkedDB string `json:"linkedDB,omitempty"`
ExternallyManaged bool `json:"externallyManaged"`
}

type PostgresRef struct {
// Postgres URL address
// +optional
URL *string `json:"url,omitempty"`
// Service information for Postgres
// +optional
Service *PostgresServiceRef `json:"service,omitempty"`
// Which versions pg will be used as backend of ferretdb
// +optional
Version *string `json:"version,omitempty"`
}

type PostgresServiceRef struct {
// +optional
Name string `json:"name,omitempty"`
// +optional
Namespace string `json:"namespace,omitempty"`
// PgPort is used because the service referred to the
// pg pod can have any port between 1 and 65535, inclusive
// but targetPort is fixed to 5432
// +optional
PgPort int32 `json:"pgPort,omitempty"`
}

// +kubebuilder:validation:Enum=server;client
type FerretDBCertificateAlias string

Expand Down
99 changes: 66 additions & 33 deletions apis/kubedb/v1alpha2/ferretdb_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
appcat "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
Expand Down Expand Up @@ -145,47 +146,80 @@ func (f *FerretDB) ValidateCreateOrUpdate() field.ErrorList {
}

// Auth secret related
if f.Spec.AuthSecret != nil && f.Spec.AuthSecret.ExternallyManaged != f.Spec.Backend.ExternallyManaged {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("authSecret"),
f.Name,
`when 'spec.backend' is internally managed, 'spec.authSecret' can't be externally managed and vice versa`))
}
if f.Spec.AuthSecret != nil && f.Spec.AuthSecret.ExternallyManaged && f.Spec.AuthSecret.Name == "" {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("authSecret"),
f.Name,
`'spec.authSecret.name' needs to specify when auth secret is externally managed`))
`'spec.authSecret.name' need to specify when auth secret is externally managed`))
}

// Termination policy related
if f.Spec.StorageType == StorageTypeEphemeral && f.Spec.DeletionPolicy == TerminationPolicyHalt {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
f.Name,
`'spec.terminationPolicy: Halt' can not be used for 'Ephemeral' storage`))
}
if f.Spec.DeletionPolicy == TerminationPolicyHalt || f.Spec.DeletionPolicy == TerminationPolicyDelete {
if f.Spec.DeletionPolicy == TerminationPolicyHalt {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("terminationPolicy"),
f.Name,
`'spec.terminationPolicy' value 'Halt' or 'Delete' is not supported yet for FerretDB`))
`'spec.terminationPolicy' value 'Halt' is not supported yet for FerretDB`))
}

// FerretDBBackend related
if f.Spec.Backend.ExternallyManaged {
if f.Spec.Backend.Postgres == nil {
if f.Spec.Backend.PostgresRef == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
`'spec.postgres' is missing when backend is externally managed`))
`'backend.postgresRef' is missing when backend is externally managed`))
} else {
if f.Spec.Backend.Postgres.URL == nil {
err := f.validateServiceRef(f.Spec.Backend.Postgres.Service)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
if f.Spec.Backend.PostgresRef.Namespace == "" {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
`'backend.postgresRef.namespace' is needed when backend is externally managed`))
}
apb := appcat.AppBinding{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{
Name: f.Spec.Backend.PostgresRef.Name,
Namespace: f.Spec.Backend.PostgresRef.Namespace,
}, &apb)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("postgresRef"),
f.Name,
err.Error(),
))
}

if apb.Spec.Secret == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
`spec.secret needed in external pg appbinding`))
}

if apb.Spec.ClientConfig.Service == nil && apb.Spec.ClientConfig.URL == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("postgresRef"),
f.Name,
`'clientConfig.url' or 'clientConfig.service' needed in the external pg appbinding`,
))
}
sslMode, err := f.GetSSLModeFromAppBinding(&apb)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("postgresRef"),
f.Name,
err.Error(),
))
}

if sslMode == PostgresSSLModeRequire || sslMode == PostgresSSLModeVerifyCA || sslMode == PostgresSSLModeVerifyFull {
if apb.Spec.ClientConfig.CABundle == nil && apb.Spec.TLSSecret == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("postgresRef"),
f.Name,
err.Error()))
"backend postgres connection is ssl encrypted but 'spec.clientConfig.caBundle' or 'spec.tlsSecret' is not provided in appbinding",
))
}
}
if (apb.Spec.ClientConfig.CABundle != nil || apb.Spec.TLSSecret != nil) && sslMode == PostgresSSLModeDisable {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("postgresRef"),
f.Name,
"no client certificate or ca bundle possible when sslMode set to disable in backend postgres",
))
}
}
} else {
if f.Spec.Backend.Postgres != nil && f.Spec.Backend.Postgres.Version != nil {
if f.Spec.Backend.Version != nil {
err := f.validatePostgresVersion()
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
Expand All @@ -201,6 +235,16 @@ func (f *FerretDB) ValidateCreateOrUpdate() field.ErrorList {
f.Name,
`'spec.sslMode' value 'allowSSL' or 'preferSSL' is not supported yet for FerretDB`))
}
if f.Spec.SSLMode == SSLModeRequireSSL && f.Spec.TLS == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("sslMode"),
f.Name,
`'spec.sslMode' is requireSSL but 'spec.tls' is not set`))
}
if f.Spec.SSLMode == SSLModeDisabled && f.Spec.TLS != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("sslMode"),
f.Name,
`'spec.tls' is can't set when 'spec.sslMode' is disabled`))
}

return allErr
}
Expand Down Expand Up @@ -240,20 +284,9 @@ func (f *FerretDB) validateFerretDBVersion() error {

func (f *FerretDB) validatePostgresVersion() error {
pgVersion := v1alpha1.PostgresVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: *f.Spec.Backend.Postgres.Version}, &pgVersion)
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: *f.Spec.Backend.Version}, &pgVersion)
if err != nil {
return errors.New("postgres version not supported in KubeDB")
}
return nil
}

func (f *FerretDB) validateServiceRef(ref *PostgresServiceRef) error {
if ref == nil {
return errors.New(`have to provide 'backend.postgres.url' or 'backend.postgres.service' when backend is externally managed`)
}
// port needs to be 0 < x < 65536
if ref.Namespace == "" || ref.Name == "" || ref.PgPort <= 0 || ref.PgPort >= 65536 {
return errors.New("pg service reference name, namespace and port(0<x<65536) needs to specify properly")
}
return nil
}
Loading

0 comments on commit b4f0c7a

Please sign in to comment.