diff --git a/api/v1/database_types.go b/api/v1/database_types.go index f08f13b..8874ecf 100644 --- a/api/v1/database_types.go +++ b/api/v1/database_types.go @@ -21,6 +21,21 @@ import ( ) // DatabaseSpec defines the desired state of Database +// +kubebuilder:validation:XValidation:rule="self.type == 'redis' || !has(self.config.mode)",message="config.mode is only valid when spec.type is redis" +// +kubebuilder:validation:XValidation:rule="self.type == 'redis' || !has(self.config.sync)",message="config.sync is only valid when spec.type is redis" +// +kubebuilder:validation:XValidation:rule="self.type == 'redis' || !has(self.config.copy)",message="config.copy is only valid when spec.type is redis" +// +kubebuilder:validation:XValidation:rule="self.type == 'redis' || !has(self.config.invoke_save)",message="config.invoke_save is only valid when spec.type is redis" +// +kubebuilder:validation:XValidation:rule="self.type == 'redis' || !has(self.config.rdb_path)",message="config.rdb_path is only valid when spec.type is redis" +// +kubebuilder:validation:XValidation:rule="self.type == 'redis' || !has(self.config.args_redis)",message="config.args_redis is only valid when spec.type is redis" +// +kubebuilder:validation:XValidation:rule="self.type == 'mongodb' || !has(self.config.auth_db)",message="config.auth_db is only valid when spec.type is mongodb" +// +kubebuilder:validation:XValidation:rule="self.type == 'mongodb' || !has(self.config.oplog)",message="config.oplog is only valid when spec.type is mongodb" +// +kubebuilder:validation:XValidation:rule="self.type == 'mssql' || !has(self.config.trust_server_certificate)",message="config.trust_server_certificate is only valid when spec.type is mssql" +// +kubebuilder:validation:XValidation:rule="self.type == 'influxdb' || !has(self.config.token)",message="config.token is only valid when spec.type is influxdb" +// +kubebuilder:validation:XValidation:rule="self.type == 'influxdb' || !has(self.config.bucket)",message="config.bucket is only valid when spec.type is influxdb" +// +kubebuilder:validation:XValidation:rule="self.type == 'influxdb' || !has(self.config.org)",message="config.org is only valid when spec.type is influxdb" +// +kubebuilder:validation:XValidation:rule="self.type == 'etcd' || !has(self.config.endpoints)",message="config.endpoints is only valid when spec.type is etcd" +// +kubebuilder:validation:XValidation:rule="self.type in ['postgresql', 'mysql', 'mariadb', 'mssql'] || !has(self.config.tables)",message="config.tables is only valid for SQL databases (postgresql, mysql, mariadb, mssql)" +// +kubebuilder:validation:XValidation:rule="self.type in ['postgresql', 'mysql', 'mariadb', 'mssql'] || !has(self.config.exclude_tables)",message="config.exclude_tables is only valid for SQL databases (postgresql, mysql, mariadb, mssql)" type DatabaseSpec struct { // Type is the database backend type // +kubebuilder:validation:Enum=postgresql;mysql;mariadb;mongodb;redis;mssql;influxdb;etcd diff --git a/charts/gobackup-operator/crds/gobackup.io_databases.yaml b/charts/gobackup-operator/crds/gobackup.io_databases.yaml index 830c96a..f2cba93 100644 --- a/charts/gobackup-operator/crds/gobackup.io_databases.yaml +++ b/charts/gobackup-operator/crds/gobackup.io_databases.yaml @@ -155,6 +155,40 @@ spec: - config - type type: object + x-kubernetes-validations: + - message: config.mode is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.mode) + - message: config.sync is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.sync) + - message: config.copy is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.copy) + - message: config.invoke_save is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.invoke_save) + - message: config.rdb_path is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.rdb_path) + - message: config.args_redis is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.args_redis) + - message: config.auth_db is only valid when spec.type is mongodb + rule: self.type == 'mongodb' || !has(self.config.auth_db) + - message: config.oplog is only valid when spec.type is mongodb + rule: self.type == 'mongodb' || !has(self.config.oplog) + - message: config.trust_server_certificate is only valid when spec.type + is mssql + rule: self.type == 'mssql' || !has(self.config.trust_server_certificate) + - message: config.token is only valid when spec.type is influxdb + rule: self.type == 'influxdb' || !has(self.config.token) + - message: config.bucket is only valid when spec.type is influxdb + rule: self.type == 'influxdb' || !has(self.config.bucket) + - message: config.org is only valid when spec.type is influxdb + rule: self.type == 'influxdb' || !has(self.config.org) + - message: config.endpoints is only valid when spec.type is etcd + rule: self.type == 'etcd' || !has(self.config.endpoints) + - message: config.tables is only valid for SQL databases (postgresql, + mysql, mariadb, mssql) + rule: self.type in ['postgresql', 'mysql', 'mariadb', 'mssql'] || !has(self.config.tables) + - message: config.exclude_tables is only valid for SQL databases (postgresql, + mysql, mariadb, mssql) + rule: self.type in ['postgresql', 'mysql', 'mariadb', 'mssql'] || !has(self.config.exclude_tables) status: description: DatabaseStatus defines the observed state of Database type: object diff --git a/config/crd/bases/gobackup.io_databases.yaml b/config/crd/bases/gobackup.io_databases.yaml index 744fe2f..8a8a022 100644 --- a/config/crd/bases/gobackup.io_databases.yaml +++ b/config/crd/bases/gobackup.io_databases.yaml @@ -156,6 +156,40 @@ spec: - config - type type: object + x-kubernetes-validations: + - message: config.mode is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.mode) + - message: config.sync is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.sync) + - message: config.copy is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.copy) + - message: config.invoke_save is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.invoke_save) + - message: config.rdb_path is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.rdb_path) + - message: config.args_redis is only valid when spec.type is redis + rule: self.type == 'redis' || !has(self.config.args_redis) + - message: config.auth_db is only valid when spec.type is mongodb + rule: self.type == 'mongodb' || !has(self.config.auth_db) + - message: config.oplog is only valid when spec.type is mongodb + rule: self.type == 'mongodb' || !has(self.config.oplog) + - message: config.trust_server_certificate is only valid when spec.type + is mssql + rule: self.type == 'mssql' || !has(self.config.trust_server_certificate) + - message: config.token is only valid when spec.type is influxdb + rule: self.type == 'influxdb' || !has(self.config.token) + - message: config.bucket is only valid when spec.type is influxdb + rule: self.type == 'influxdb' || !has(self.config.bucket) + - message: config.org is only valid when spec.type is influxdb + rule: self.type == 'influxdb' || !has(self.config.org) + - message: config.endpoints is only valid when spec.type is etcd + rule: self.type == 'etcd' || !has(self.config.endpoints) + - message: config.tables is only valid for SQL databases (postgresql, + mysql, mariadb, mssql) + rule: self.type in ['postgresql', 'mysql', 'mariadb', 'mssql'] || !has(self.config.tables) + - message: config.exclude_tables is only valid for SQL databases (postgresql, + mysql, mariadb, mssql) + rule: self.type in ['postgresql', 'mysql', 'mariadb', 'mssql'] || !has(self.config.exclude_tables) status: description: DatabaseStatus defines the observed state of Database type: object diff --git a/config/crd/bases/gobackup.io_storages.yaml b/config/crd/bases/gobackup.io_storages.yaml index 6e2ddd4..78fe7ea 100644 --- a/config/crd/bases/gobackup.io_storages.yaml +++ b/config/crd/bases/gobackup.io_storages.yaml @@ -59,8 +59,12 @@ spec: a valid secret key. type: string 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 type: string optional: @@ -106,8 +110,12 @@ spec: a valid secret key. type: string 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 type: string optional: @@ -143,8 +151,12 @@ spec: a valid secret key. type: string 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 type: string optional: @@ -195,8 +207,12 @@ spec: a valid secret key. type: string 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 type: string optional: @@ -222,8 +238,12 @@ spec: a valid secret key. type: string 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 type: string optional: @@ -259,8 +279,12 @@ spec: a valid secret key. type: string 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 type: string optional: @@ -296,8 +320,12 @@ spec: a valid secret key. type: string 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 type: string optional: diff --git a/pkg/k8sutil/secret.go b/pkg/k8sutil/secret.go index d080669..3730485 100644 --- a/pkg/k8sutil/secret.go +++ b/pkg/k8sutil/secret.go @@ -47,7 +47,6 @@ func (k *K8s) CreateSecret(ctx context.Context, backup *backupv1.Backup) error { // Process database references for _, database := range model.DatabaseRefs { - dbType := strings.ToLower(database.Type) // Resource name is always "databases" (plural of Database kind), not type-specific resource := "databases" @@ -60,7 +59,7 @@ func (k *K8s) CreateSecret(ctx context.Context, backup *backupv1.Backup) error { // Fetch the database CRD databaseCRD, err := k.GetCRD(ctx, apiGroup, "v1", resource, namespace, database.Name) if err != nil { - return fmt.Errorf("failed to get %s database: %w", dbType, err) + return fmt.Errorf("failed to get database %s: %w", database.Name, err) } // Extract the database spec @@ -69,6 +68,12 @@ func (k *K8s) CreateSecret(ctx context.Context, backup *backupv1.Backup) error { return fmt.Errorf("database spec for %s is not a valid map", database.Name) } + rawType, ok := specMap["type"].(string) + if !ok || strings.TrimSpace(rawType) == "" { + return fmt.Errorf("database type for %s is missing or invalid", database.Name) + } + dbType := strings.ToLower(strings.TrimSpace(rawType)) + // Extract config if it exists configMap, ok := specMap["config"].(map[string]interface{}) if !ok {