From 3ec49452a2c3cd69b31ee7d2200e74b680bd2f8d Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Wed, 11 Jan 2023 15:34:05 +0100 Subject: [PATCH 1/3] Squashed commits to update username for commits --- Makefile | 2 +- api/v1alpha1/database_types.go | 1 + .../db-operator/crds/kci.rocks_databases.yaml | 327 ++++++++++++++++++ .../crds/kci.rocks_dbinstances.yaml | 196 +++++++++++ config/crd/bases/kci.rocks_databases.yaml | 2 + controllers/backup/cronjob.go | 9 +- controllers/backup/cronjob_test.go | 45 ++- controllers/database_controller.go | 88 ++--- controllers/database_helper.go | 15 +- controllers/database_helper_test.go | 12 +- controllers/dbinstance_controller.go | 9 +- controllers/proxy_helper.go | 5 +- docs/creatingdatabases.md | 11 + pkg/utils/kci/decorator.go | 18 +- pkg/utils/kci/decorator_test.go | 64 +++- pkg/utils/proxy/cloudproxy.go | 29 +- pkg/utils/proxy/create.go | 17 +- pkg/utils/proxy/types.go | 9 +- 18 files changed, 755 insertions(+), 104 deletions(-) create mode 100644 charts/db-operator/crds/kci.rocks_databases.yaml create mode 100644 charts/db-operator/crds/kci.rocks_dbinstances.yaml diff --git a/Makefile b/Makefile index ed1b3bf8..265febc0 100644 --- a/Makefile +++ b/Makefile @@ -85,7 +85,7 @@ TMP_DIR=$$(mktemp -d) ;\ cd $$TMP_DIR ;\ go mod init tmp ;\ echo "Downloading $(2)" ;\ -GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ +GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ rm -rf $$TMP_DIR ;\ } endef diff --git a/api/v1alpha1/database_types.go b/api/v1alpha1/database_types.go index c6716020..70cb109e 100644 --- a/api/v1alpha1/database_types.go +++ b/api/v1alpha1/database_types.go @@ -38,6 +38,7 @@ type DatabaseSpec struct { ConnectionStringTemplate string `json:"connectionStringTemplate,omitempty"` SecretsTemplates map[string]string `json:"secretsTemplates,omitempty"` Postgres Postgres `json:"postgres,omitempty"` + Cleanup bool `json:"cleanup,omitempty"` } // Postgres struct should be used to provide resource that only applicable to postgres diff --git a/charts/db-operator/crds/kci.rocks_databases.yaml b/charts/db-operator/crds/kci.rocks_databases.yaml new file mode 100644 index 00000000..5bde21d8 --- /dev/null +++ b/charts/db-operator/crds/kci.rocks_databases.yaml @@ -0,0 +1,327 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: databases.kci.rocks +spec: + group: kci.rocks + names: + kind: Database + listKind: DatabaseList + plural: databases + shortNames: + - db + singular: database + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: current db phase + jsonPath: .status.phase + name: Phase + type: string + - description: current db status + jsonPath: .status.status + name: Status + type: boolean + - description: If database is protected to not get deleted. + jsonPath: .spec.deletionProtected + name: Protected + type: boolean + - description: instance reference + jsonPath: .spec.instance + name: DBInstance + type: string + - description: time since creation of resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Database is the Schema for the databases API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: DatabaseSpec defines the desired state of Database + properties: + backup: + description: DatabaseBackup defines the desired state of backup and + schedule + properties: + cron: + type: string + enable: + type: boolean + required: + - cron + - enable + type: object + cleanup: + type: boolean + connectionStringTemplate: + description: 'ConnectionStringTemplate field can be used to pass a + custom template for generating a db connection string. These keywords + can be used: Protocol, DatabaseHost, DatabasePort, UserName, Password, + DatabaseName. Default template looks like this: "{{ .Protocol }}://{{ + .UserName }}:{{ .Password }}@{{ .DatabaseHost }}:{{ .DatabasePort + }}/{{ .DatabaseName }}"' + type: string + deletionProtected: + type: boolean + extensions: + items: + type: string + type: array + instance: + type: string + postgres: + description: Postgres struct should be used to provide resource that + only applicable to postgres + properties: + dropPublicSchema: + description: If set to true, the public schema will be dropped + after the database creation + type: boolean + schemas: + description: Specify schemas to be created. The user created by + db-operator will have all access on them. + items: + type: string + type: array + type: object + secretName: + type: string + secretsTemplates: + additionalProperties: + type: string + type: object + required: + - backup + - deletionProtected + - instance + - secretName + type: object + status: + description: DatabaseStatus defines the observed state of Database + properties: + database: + type: string + instanceRef: + description: DbInstance is the Schema for the dbinstances API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this + representation of an object. Servers should convert recognized + schemas to the latest internal value, and may reject unrecognized + values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource + this object represents. Servers may infer this from the endpoint + the client submits requests to. Cannot be updated. In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: DbInstanceSpec defines the desired state of DbInstance + properties: + adminSecretRef: + description: NamespacedName is a fork of the kubernetes api + type of the same name. Sadly this is required because CRD + structs must have all fields json tagged and the kubernetes + type is not tagged. + properties: + Name: + type: string + Namespace: + type: string + required: + - Name + - Namespace + type: object + backup: + description: DbInstanceBackup defines name of google bucket + to use for storing database dumps for backup when backup + is enabled + properties: + bucket: + type: string + required: + - bucket + type: object + engine: + description: 'Important: Run "make generate" to regenerate + code after modifying this file' + type: string + generic: + description: GenericInstance is used when instance type is + generic and describes necessary informations to use instance + generic instance can be any backend, it must be reachable + by described address and port + properties: + backupHost: + description: BackupHost address will be used for dumping + database for backup Usually secondary address for primary-secondary + setup or cluster lb address If it's not defined, above + Host will be used as backup host address. + type: string + host: + type: string + port: + type: integer + publicIp: + type: string + required: + - host + - port + type: object + google: + description: GoogleInstance is used when instance type is + Google Cloud SQL and describes necessary informations to + use google API to create sql instances + properties: + apiEndpoint: + type: string + clientSecretRef: + description: NamespacedName is a fork of the kubernetes + api type of the same name. Sadly this is required because + CRD structs must have all fields json tagged and the + kubernetes type is not tagged. + properties: + Name: + type: string + Namespace: + type: string + required: + - Name + - Namespace + type: object + configmapRef: + description: NamespacedName is a fork of the kubernetes + api type of the same name. Sadly this is required because + CRD structs must have all fields json tagged and the + kubernetes type is not tagged. + properties: + Name: + type: string + Namespace: + type: string + required: + - Name + - Namespace + type: object + instance: + type: string + required: + - configmapRef + - instance + type: object + monitoring: + description: DbInstanceMonitoring defines if exporter + properties: + enabled: + type: boolean + required: + - enabled + type: object + sslConnection: + description: DbInstanceSSLConnection defines weather connection + from db-operator to instance has to be ssl or not + properties: + enabled: + type: boolean + skip-verify: + description: SkipVerity use SSL connection, but don't + check against a CA + type: boolean + required: + - enabled + - skip-verify + type: object + required: + - adminSecretRef + - engine + type: object + status: + description: DbInstanceStatus defines the observed state of DbInstance + properties: + checksums: + additionalProperties: + type: string + type: object + info: + additionalProperties: + type: string + type: object + phase: + description: 'Important: Run "make generate" to regenerate + code after modifying this file' + type: string + status: + type: boolean + required: + - phase + - status + type: object + type: object + monitorUserSecret: + type: string + phase: + description: 'Important: Run "make generate" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + proxyStatus: + description: DatabaseProxyStatus defines whether proxy for database + is enabled or not if so, provide information + properties: + serviceName: + type: string + sqlPort: + format: int32 + type: integer + status: + type: boolean + required: + - serviceName + - sqlPort + - status + type: object + status: + type: boolean + user: + type: string + required: + - database + - instanceRef + - phase + - status + - user + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/db-operator/crds/kci.rocks_dbinstances.yaml b/charts/db-operator/crds/kci.rocks_dbinstances.yaml new file mode 100644 index 00000000..65061e90 --- /dev/null +++ b/charts/db-operator/crds/kci.rocks_dbinstances.yaml @@ -0,0 +1,196 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: dbinstances.kci.rocks +spec: + group: kci.rocks + names: + kind: DbInstance + listKind: DbInstanceList + plural: dbinstances + shortNames: + - dbin + singular: dbinstance + scope: Cluster + versions: + - additionalPrinterColumns: + - description: current phase + jsonPath: .status.phase + name: Phase + type: string + - description: health status + jsonPath: .status.status + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: DbInstance is the Schema for the dbinstances API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: DbInstanceSpec defines the desired state of DbInstance + properties: + adminSecretRef: + description: NamespacedName is a fork of the kubernetes api type of + the same name. Sadly this is required because CRD structs must have + all fields json tagged and the kubernetes type is not tagged. + properties: + Name: + type: string + Namespace: + type: string + required: + - Name + - Namespace + type: object + backup: + description: DbInstanceBackup defines name of google bucket to use + for storing database dumps for backup when backup is enabled + properties: + bucket: + type: string + required: + - bucket + type: object + engine: + description: 'Important: Run "make generate" to regenerate code after + modifying this file' + type: string + generic: + description: GenericInstance is used when instance type is generic + and describes necessary informations to use instance generic instance + can be any backend, it must be reachable by described address and + port + properties: + backupHost: + description: BackupHost address will be used for dumping database + for backup Usually secondary address for primary-secondary setup + or cluster lb address If it's not defined, above Host will be + used as backup host address. + type: string + host: + type: string + port: + type: integer + publicIp: + type: string + required: + - host + - port + type: object + google: + description: GoogleInstance is used when instance type is Google Cloud + SQL and describes necessary informations to use google API to create + sql instances + properties: + apiEndpoint: + type: string + clientSecretRef: + description: NamespacedName is a fork of the kubernetes api type + of the same name. Sadly this is required because CRD structs + must have all fields json tagged and the kubernetes type is + not tagged. + properties: + Name: + type: string + Namespace: + type: string + required: + - Name + - Namespace + type: object + configmapRef: + description: NamespacedName is a fork of the kubernetes api type + of the same name. Sadly this is required because CRD structs + must have all fields json tagged and the kubernetes type is + not tagged. + properties: + Name: + type: string + Namespace: + type: string + required: + - Name + - Namespace + type: object + instance: + type: string + required: + - configmapRef + - instance + type: object + monitoring: + description: DbInstanceMonitoring defines if exporter + properties: + enabled: + type: boolean + required: + - enabled + type: object + sslConnection: + description: DbInstanceSSLConnection defines weather connection from + db-operator to instance has to be ssl or not + properties: + enabled: + type: boolean + skip-verify: + description: SkipVerity use SSL connection, but don't check against + a CA + type: boolean + required: + - enabled + - skip-verify + type: object + required: + - adminSecretRef + - engine + type: object + status: + description: DbInstanceStatus defines the observed state of DbInstance + properties: + checksums: + additionalProperties: + type: string + type: object + info: + additionalProperties: + type: string + type: object + phase: + description: 'Important: Run "make generate" to regenerate code after + modifying this file' + type: string + status: + type: boolean + required: + - phase + - status + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/kci.rocks_databases.yaml b/config/crd/bases/kci.rocks_databases.yaml index 9c5b14f3..5bde21d8 100644 --- a/config/crd/bases/kci.rocks_databases.yaml +++ b/config/crd/bases/kci.rocks_databases.yaml @@ -71,6 +71,8 @@ spec: - cron - enable type: object + cleanup: + type: boolean connectionStringTemplate: description: 'ConnectionStringTemplate field can be used to pass a custom template for generating a db connection string. These keywords diff --git a/controllers/backup/cronjob.go b/controllers/backup/cronjob.go index eb3aa5e1..74a7e324 100644 --- a/controllers/backup/cronjob.go +++ b/controllers/backup/cronjob.go @@ -34,7 +34,7 @@ import ( // GCSBackupCron builds kubernetes cronjob object // to create database backup regularly with defined schedule from dbcr // this job will database dump and upload to google bucket storage for backup -func GCSBackupCron(conf *config.Config, dbcr *kciv1alpha1.Database) (*batchv1beta1.CronJob, error) { +func GCSBackupCron(conf *config.Config, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) (*batchv1beta1.CronJob, error) { cronJobSpec, err := buildCronJobSpec(conf, dbcr) if err != nil { return nil, err @@ -46,9 +46,10 @@ func GCSBackupCron(conf *config.Config, dbcr *kciv1alpha1.Database) (*batchv1bet APIVersion: "batch", }, ObjectMeta: metav1.ObjectMeta{ - Name: dbcr.Namespace + "-" + dbcr.Name + "-" + "backup", - Namespace: dbcr.Namespace, - Labels: kci.BaseLabelBuilder(), + Name: dbcr.Namespace + "-" + dbcr.Name + "-" + "backup", + Namespace: dbcr.Namespace, + Labels: kci.BaseLabelBuilder(), + OwnerReferences: ownership, }, Spec: cronJobSpec, }, nil diff --git a/controllers/backup/cronjob_test.go b/controllers/backup/cronjob_test.go index 5416d78a..01893f0b 100644 --- a/controllers/backup/cronjob_test.go +++ b/controllers/backup/cronjob_test.go @@ -26,9 +26,11 @@ import ( "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestGCSBackupCronGsql(t *testing.T) { + ownership := []metav1.OwnerReference{} dbcr := &kciv1alpha1.Database{} dbcr.Namespace = "TestNS" dbcr.Name = "TestDB" @@ -43,7 +45,7 @@ func TestGCSBackupCronGsql(t *testing.T) { conf := config.LoadConfig() instance.Spec.Engine = "postgres" - funcCronObject, err := GCSBackupCron(&conf, dbcr) + funcCronObject, err := GCSBackupCron(&conf, dbcr, ownership) if err != nil { fmt.Print(err) } @@ -51,7 +53,7 @@ func TestGCSBackupCronGsql(t *testing.T) { assert.Equal(t, "postgresbackupimage:latest", funcCronObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image) instance.Spec.Engine = "mysql" - funcCronObject, err = GCSBackupCron(&conf, dbcr) + funcCronObject, err = GCSBackupCron(&conf, dbcr, ownership) if err != nil { fmt.Print(err) } @@ -64,6 +66,7 @@ func TestGCSBackupCronGsql(t *testing.T) { } func TestGCSBackupCronGeneric(t *testing.T) { + ownership := []metav1.OwnerReference{} dbcr := &kciv1alpha1.Database{} dbcr.Namespace = "TestNS" dbcr.Name = "TestDB" @@ -78,7 +81,7 @@ func TestGCSBackupCronGeneric(t *testing.T) { conf := config.LoadConfig() instance.Spec.Engine = "postgres" - funcCronObject, err := GCSBackupCron(&conf, dbcr) + funcCronObject, err := GCSBackupCron(&conf, dbcr, ownership) if err != nil { fmt.Print(err) } @@ -86,7 +89,7 @@ func TestGCSBackupCronGeneric(t *testing.T) { assert.Equal(t, "postgresbackupimage:latest", funcCronObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image) instance.Spec.Engine = "mysql" - funcCronObject, err = GCSBackupCron(&conf, dbcr) + funcCronObject, err = GCSBackupCron(&conf, dbcr, ownership) if err != nil { fmt.Print(err) } @@ -96,8 +99,42 @@ func TestGCSBackupCronGeneric(t *testing.T) { assert.Equal(t, "TestNS", funcCronObject.Namespace) assert.Equal(t, "TestNS-TestDB-backup", funcCronObject.Name) assert.Equal(t, "* * * * *", funcCronObject.Spec.Schedule) + assert.Equal(t, len(funcCronObject.OwnerReferences), 0, "Unexpected size of an OwnerReference") } +func TestGCSBackupCronGenericWithOwnerReference(t *testing.T) { + ownership := []metav1.OwnerReference{} + ownership = append(ownership, metav1.OwnerReference{ + APIVersion: "api-version", + Kind: "kind", + Name: "name", + UID: "uid", + }) + dbcr := &kciv1alpha1.Database{} + dbcr.Namespace = "TestNS" + dbcr.Name = "TestDB" + instance := &kciv1alpha1.DbInstance{} + instance.Status.Info = map[string]string{"DB_CONN": "TestConnection", "DB_PORT": "1234"} + instance.Spec.Generic = &kciv1alpha1.GenericInstance{BackupHost: "slave.test"} + dbcr.Status.InstanceRef = instance + dbcr.Spec.Instance = "staging" + dbcr.Spec.Backup.Cron = "* * * * *" + + os.Setenv("CONFIG_PATH", "./test/backup_config.yaml") + conf := config.LoadConfig() + + instance.Spec.Engine = "postgres" + funcCronObject, err := GCSBackupCron(&conf, dbcr, ownership) + if err != nil { + fmt.Print(err) + } + + assert.Equal(t, len(funcCronObject.OwnerReferences), 1, "Unexpected size of an OwnerReference") + assert.Equal(t, funcCronObject.OwnerReferences[0].APIVersion, ownership[0].APIVersion, "API Version in the OwnerReference is wrong") + assert.Equal(t, funcCronObject.OwnerReferences[0].Kind, ownership[0].Kind, "Kind in the OwnerReference is wrong") + assert.Equal(t, funcCronObject.OwnerReferences[0].Name, ownership[0].Name, "Name in the OwnerReference is wrong") + assert.Equal(t, funcCronObject.OwnerReferences[0].UID, ownership[0].UID, "UID in the OwnerReference is wrong") +} func TestGetResourceRequirements(t *testing.T) { os.Setenv("CONFIG_PATH", "./test/backup_config.yaml") conf := config.LoadConfig() diff --git a/controllers/database_controller.go b/controllers/database_controller.go index 8ac2669d..fa779a1e 100644 --- a/controllers/database_controller.go +++ b/controllers/database_controller.go @@ -179,37 +179,47 @@ func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // database status not true, process phase if !dbcr.Status.Status { + ownership := []metav1.OwnerReference{} + if dbcr.Spec.Cleanup { + ownership = append(ownership, metav1.OwnerReference{ + APIVersion: dbcr.APIVersion, + Kind: dbcr.Kind, + Name: dbcr.Name, + UID: dbcr.GetUID(), + }, + ) + } + phase := dbcr.Status.Phase logrus.Infof("DB: namespace=%s, name=%s start %s", dbcr.Namespace, dbcr.Name, phase) defer promDBsPhaseTime.WithLabelValues(phase).Observe(kci.TimeTrack(time.Now())) - err := r.createDatabase(ctx, dbcr) + err := r.createDatabase(ctx, dbcr, ownership) if err != nil { // when database creation failed, don't requeue request. to prevent exceeding api limit (ex: against google api) return r.manageError(ctx, dbcr, err, false) } + dbcr.Status.Phase = dbPhaseInstanceAccessSecret - err = r.createInstanceAccessSecret(ctx, dbcr) - if err != nil { + + if err = r.createInstanceAccessSecret(ctx, dbcr, ownership); err != nil { return r.manageError(ctx, dbcr, err, true) } dbcr.Status.Phase = dbPhaseProxy - err = r.createProxy(ctx, dbcr) + err = r.createProxy(ctx, dbcr, ownership) if err != nil { return r.manageError(ctx, dbcr, err, true) } dbcr.Status.Phase = dbPhaseSecretsTemplating - err = r.createTemplatedSecrets(ctx, dbcr) - if err != nil { + if err = r.createTemplatedSecrets(ctx, dbcr, ownership); err != nil { return r.manageError(ctx, dbcr, err, true) } dbcr.Status.Phase = dbPhaseConfigMap - err = r.createInfoConfigMap(ctx, dbcr) - if err != nil { + if err = r.createInfoConfigMap(ctx, dbcr, ownership); err != nil { return r.manageError(ctx, dbcr, err, true) } dbcr.Status.Phase = dbPhaseBackupJob - err = r.createBackupJob(ctx, dbcr) + err = r.createBackupJob(ctx, dbcr, ownership) if err != nil { return r.manageError(ctx, dbcr, err, true) } @@ -278,7 +288,7 @@ func (r *DatabaseReconciler) initialize(ctx context.Context, dbcr *kciv1alpha1.D } // createDatabase secret, actual database using admin secret -func (r *DatabaseReconciler) createDatabase(ctx context.Context, dbcr *kciv1alpha1.Database) error { +func (r *DatabaseReconciler) createDatabase(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error { databaseSecret, err := r.getDatabaseSecret(ctx, dbcr) if err != nil { if k8serrors.IsNotFound(err) { @@ -287,7 +297,7 @@ func (r *DatabaseReconciler) createDatabase(ctx context.Context, dbcr *kciv1alph logrus.Errorf("can not generate credentials for database - %s", err) return err } - newDatabaseSecret := kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.Namespace, secretData) + newDatabaseSecret := kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.Namespace, secretData, ownership) err = r.Create(ctx, newDatabaseSecret) if err != nil { // failed to create secret @@ -388,7 +398,7 @@ func (r *DatabaseReconciler) deleteDatabase(ctx context.Context, dbcr *kciv1alph return nil } -func (r *DatabaseReconciler) createInstanceAccessSecret(ctx context.Context, dbcr *kciv1alpha1.Database) error { +func (r *DatabaseReconciler) createInstanceAccessSecret(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error { if backend, _ := dbcr.GetBackendType(); backend != "google" { logrus.Debugf("DB: namespace=%s, name=%s %s doesn't need instance access secret skipping...", dbcr.Namespace, dbcr.Name, backend) return nil @@ -422,7 +432,7 @@ func (r *DatabaseReconciler) createInstanceAccessSecret(ctx context.Context, dbc secretData[credFile] = data newName := dbcr.InstanceAccessSecretName() - newSecret := kci.SecretBuilder(newName, dbcr.GetNamespace(), secretData) + newSecret := kci.SecretBuilder(newName, dbcr.GetNamespace(), secretData, ownership) err = r.Create(ctx, newSecret) if err != nil { @@ -442,7 +452,7 @@ func (r *DatabaseReconciler) createInstanceAccessSecret(ctx context.Context, dbc return nil } -func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1.Database) error { +func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error { backend, _ := dbcr.GetBackendType() if backend == "generic" { logrus.Infof("DB: namespace=%s, name=%s %s proxy creation is not yet implemented skipping...", dbcr.Namespace, dbcr.Name, backend) @@ -455,7 +465,7 @@ func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1. } // create proxy configmap - cm, err := proxy.BuildConfigmap(proxyInterface) + cm, err := proxy.BuildConfigmap(proxyInterface, ownership) if err != nil { return err } @@ -478,7 +488,7 @@ func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1. } // create proxy deployment - deploy, err := proxy.BuildDeployment(proxyInterface) + deploy, err := proxy.BuildDeployment(proxyInterface, ownership) if err != nil { return err } @@ -499,7 +509,7 @@ func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1. } // create proxy service - svc, err := proxy.BuildService(proxyInterface) + svc, err := proxy.BuildService(proxyInterface, ownership) if err != nil { return err } @@ -533,7 +543,7 @@ func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1. if isMonitoringEnabled && inCrdList(crdList, "servicemonitors.monitoring.coreos.com") { // create proxy PromServiceMonitor - promSvcMon, err := proxy.BuildServiceMonitor(proxyInterface) + promSvcMon, err := proxy.BuildServiceMonitor(proxyInterface, ownership) if err != nil { return err } @@ -567,15 +577,17 @@ func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1. return nil } -func (r *DatabaseReconciler) createTemplatedSecrets(ctx context.Context, dbcr *kciv1alpha1.Database) error { +func (r *DatabaseReconciler) createTemplatedSecrets(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error { // First of all the password should be taken from secret because it's not stored anywhere else databaseSecret, err := r.getDatabaseSecret(ctx, dbcr) if err != nil { return err } // Then parse the secret to get the password - // Connection stirng is deprecated and will be removed soon. So this switch is temporary. + // Connection string is deprecated and will be removed soon. So this switch is temporary. // Once connection string is removed, the switch and the following if condition are gone + // Connection String doesn't support the cleaning up feature, so the secret with a connection + // string won't be removed after a db resource is removed. useLegacyConnectionString := false switch { case len(dbcr.Spec.ConnectionStringTemplate) > 0 && len(dbcr.Spec.SecretsTemplates) > 0: @@ -610,7 +622,10 @@ func (r *DatabaseReconciler) createTemplatedSecrets(ctx context.Context, dbcr *k } logrus.Debugf("DB: namespace=%s, name=%s updating credentials secret", dbcr.Namespace, dbcr.Name) newSecret := addConnectionStringToSecret(dbcr, databaseSecret.Data, dbConnectionString) - return r.Update(ctx, newSecret, &client.UpdateOptions{}) + if err = r.Update(ctx, newSecret, &client.UpdateOptions{}); err != nil { + return err + } + return nil } dbSecrets, err := generateTemplatedSecrets(dbcr, databaseCred) @@ -618,21 +633,20 @@ func (r *DatabaseReconciler) createTemplatedSecrets(ctx context.Context, dbcr *k return err } // Adding values - newSecret := fillTemplatedSecretData(dbcr, databaseSecret.Data, dbSecrets) - err = r.Update(ctx, newSecret, &client.UpdateOptions{}) - if err != nil { + newSecret := fillTemplatedSecretData(dbcr, databaseSecret.Data, dbSecrets, ownership) + if err = r.Update(ctx, newSecret, &client.UpdateOptions{}); err != nil { return err } - newSecret = removeObsoleteSecret(dbcr, databaseSecret.Data, dbSecrets) - err = r.Update(ctx, newSecret, &client.UpdateOptions{}) - if err != nil { + + newSecret = removeObsoleteSecret(dbcr, databaseSecret.Data, dbSecrets, ownership) + if err = r.Update(ctx, newSecret, &client.UpdateOptions{}); err != nil { return err } return nil } -func (r *DatabaseReconciler) createInfoConfigMap(ctx context.Context, dbcr *kciv1alpha1.Database) error { +func (r *DatabaseReconciler) createInfoConfigMap(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error { instance, err := dbcr.GetInstanceRef() if err != nil { return err @@ -645,19 +659,7 @@ func (r *DatabaseReconciler) createInfoConfigMap(ctx context.Context, dbcr *kciv info["DB_HOST"] = proxyStatus.ServiceName info["DB_PORT"] = strconv.FormatInt(int64(proxyStatus.SQLPort), 10) } - - databaseConfigResource := &corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: dbcr.Namespace, - Name: dbcr.Spec.SecretName, - Labels: kci.BaseLabelBuilder(), - }, - Data: info, - } + databaseConfigResource := kci.ConfigMapBuilder(dbcr.Spec.SecretName, dbcr.Namespace, info, ownership) err = r.Create(ctx, databaseConfigResource) if err != nil { @@ -678,13 +680,13 @@ func (r *DatabaseReconciler) createInfoConfigMap(ctx context.Context, dbcr *kciv return nil } -func (r *DatabaseReconciler) createBackupJob(ctx context.Context, dbcr *kciv1alpha1.Database) error { +func (r *DatabaseReconciler) createBackupJob(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error { if !dbcr.Spec.Backup.Enable { // if not enabled, skip return nil } - cronjob, err := backup.GCSBackupCron(r.Conf, dbcr) + cronjob, err := backup.GCSBackupCron(r.Conf, dbcr, ownership) if err != nil { return err } diff --git a/controllers/database_helper.go b/controllers/database_helper.go index b4c983be..4573b5fb 100644 --- a/controllers/database_helper.go +++ b/controllers/database_helper.go @@ -27,6 +27,7 @@ import ( "github.com/kloeckner-i/db-operator/pkg/utils/kci" "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/strings/slices" ) @@ -356,7 +357,7 @@ func generateTemplatedSecrets(dbcr *kciv1alpha1.Database, databaseCred database. return secrets, nil } -func fillTemplatedSecretData(dbcr *kciv1alpha1.Database, secretData map[string][]byte, newSecretFields map[string]string) (newSecret *v1.Secret) { +func fillTemplatedSecretData(dbcr *kciv1alpha1.Database, secretData map[string][]byte, newSecretFields map[string]string, ownership []metav1.OwnerReference) (newSecret *v1.Secret) { blockedTempatedKeys := getBlockedTempatedKeys() for key, value := range newSecretFields { if slices.Contains(blockedTempatedKeys, key) { @@ -366,7 +367,7 @@ func fillTemplatedSecretData(dbcr *kciv1alpha1.Database, secretData map[string][ key, ) } else { - newSecret = addTemplatedSecretToSecret(dbcr, secretData, key, value) + newSecret = addTemplatedSecretToSecret(dbcr, secretData, key, value, ownership) } } return @@ -374,15 +375,15 @@ func fillTemplatedSecretData(dbcr *kciv1alpha1.Database, secretData map[string][ func addConnectionStringToSecret(dbcr *kciv1alpha1.Database, secretData map[string][]byte, connectionString string) *v1.Secret { secretData["CONNECTION_STRING"] = []byte(connectionString) - return kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.GetNamespace(), secretData) + return kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.GetNamespace(), secretData, []metav1.OwnerReference{}) } -func addTemplatedSecretToSecret(dbcr *kciv1alpha1.Database, secretData map[string][]byte, secretName string, secretValue string) *v1.Secret { +func addTemplatedSecretToSecret(dbcr *kciv1alpha1.Database, secretData map[string][]byte, secretName string, secretValue string, ownership []metav1.OwnerReference) *v1.Secret { secretData[secretName] = []byte(secretValue) - return kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.GetNamespace(), secretData) + return kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.GetNamespace(), secretData, ownership) } -func removeObsoleteSecret(dbcr *kciv1alpha1.Database, secretData map[string][]byte, newSecretFields map[string]string) *v1.Secret { +func removeObsoleteSecret(dbcr *kciv1alpha1.Database, secretData map[string][]byte, newSecretFields map[string]string, ownership []metav1.OwnerReference) *v1.Secret { blockedTempatedKeys := getBlockedTempatedKeys() for key := range secretData { @@ -395,5 +396,5 @@ func removeObsoleteSecret(dbcr *kciv1alpha1.Database, secretData map[string][]by } } - return kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.GetNamespace(), secretData) + return kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.GetNamespace(), secretData, ownership) } diff --git a/controllers/database_helper_test.go b/controllers/database_helper_test.go index 7ca6917a..0b1f6624 100644 --- a/controllers/database_helper_test.go +++ b/controllers/database_helper_test.go @@ -23,9 +23,13 @@ import ( "github.com/kloeckner-i/db-operator/pkg/utils/database" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + ) var testDbcred = database.Credentials{Name: "testdb", Username: "testuser", Password: "password"} +var ownership = []metav1.OwnerReference{} func TestDeterminPostgresType(t *testing.T) { postgresDbCr := newPostgresTestDbCr(newPostgresTestDbInstanceCr()) @@ -201,7 +205,7 @@ func TestAddingTemplatedSecretsToSecret(t *testing.T) { connectionString := "it's a dummy connection string" - secret := addTemplatedSecretToSecret(postgresDbCr, secretData, "TMPL", connectionString) + secret := addTemplatedSecretToSecret(postgresDbCr, secretData, "TMPL", connectionString, ownership) secretData["CONNECTION_STRING"] = []byte(connectionString) if val, ok := secret.Data["TMPL"]; ok { assert.Equal(t, string(val), connectionString, "connections string in a secret contains unexpected values") @@ -279,7 +283,7 @@ func TestBlockedTempatedKeysGeneratation(t *testing.T) { Data: map[string][]byte{}, } - newSecret := fillTemplatedSecretData(postgresDbCr, dummySecret.Data, sercretData) + newSecret := fillTemplatedSecretData(postgresDbCr, dummySecret.Data, sercretData, ownership) assert.Equal(t, newSecret.Data, expectedData, "generated connections string is wrong") } @@ -309,8 +313,8 @@ func TestObsoleteFieldsRemoving(t *testing.T) { }, } - newSecret := fillTemplatedSecretData(postgresDbCr, dummySecret.Data, sercretData) - newSecret = removeObsoleteSecret(postgresDbCr, dummySecret.Data, sercretData) + newSecret := fillTemplatedSecretData(postgresDbCr, dummySecret.Data, sercretData, ownership) + newSecret = removeObsoleteSecret(postgresDbCr, dummySecret.Data, sercretData, ownership) assert.Equal(t, newSecret.Data, expectedData, "generated connections string is wrong") } diff --git a/controllers/dbinstance_controller.go b/controllers/dbinstance_controller.go index 428c002a..ffa390e9 100644 --- a/controllers/dbinstance_controller.go +++ b/controllers/dbinstance_controller.go @@ -30,6 +30,7 @@ import ( "github.com/kloeckner-i/db-operator/pkg/utils/proxy" "github.com/sirupsen/logrus" k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -132,7 +133,7 @@ func (r *DbInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) } dbin.Status.Phase = dbInstancePhaseProxyCreate - err = r.createProxy(ctx, dbin) + err = r.createProxy(ctx, dbin, []metav1.OwnerReference{}) if err != nil { logrus.Errorf("Instance: name=%s proxy creation failed - %s", dbin.Name, err) return reconcileResult, err @@ -242,7 +243,7 @@ func (r *DbInstanceReconciler) broadcast(ctx context.Context, dbin *kciv1alpha1. return nil } -func (r *DbInstanceReconciler) createProxy(ctx context.Context, dbin *kciv1alpha1.DbInstance) error { +func (r *DbInstanceReconciler) createProxy(ctx context.Context, dbin *kciv1alpha1.DbInstance, ownership []metav1.OwnerReference) error { proxyInterface, err := determineProxyTypeForInstance(r.Conf, dbin) if err != nil { if err == ErrNoProxySupport { @@ -252,7 +253,7 @@ func (r *DbInstanceReconciler) createProxy(ctx context.Context, dbin *kciv1alpha } // create proxy deployment - deploy, err := proxy.BuildDeployment(proxyInterface) + deploy, err := proxy.BuildDeployment(proxyInterface, ownership) if err != nil { return err } @@ -273,7 +274,7 @@ func (r *DbInstanceReconciler) createProxy(ctx context.Context, dbin *kciv1alpha } // create proxy service - svc, err := proxy.BuildService(proxyInterface) + svc, err := proxy.BuildService(proxyInterface, ownership) if err != nil { return err } diff --git a/controllers/proxy_helper.go b/controllers/proxy_helper.go index 0402d8d0..b2597a85 100644 --- a/controllers/proxy_helper.go +++ b/controllers/proxy_helper.go @@ -77,7 +77,7 @@ func determineProxyTypeForDB(conf *config.Config, dbcr *kciv1alpha1.Database) (p return nil, err } - return &proxy.CloudProxy{ + proxy := &proxy.CloudProxy{ NamePrefix: "db-" + dbcr.Name, Namespace: dbcr.Namespace, InstanceConnectionName: instance.Status.Info["DB_CONN"], @@ -87,7 +87,8 @@ func determineProxyTypeForDB(conf *config.Config, dbcr *kciv1alpha1.Database) (p Labels: kci.LabelBuilder(labels), Conf: conf, MonitoringEnabled: monitoringEnabled, - }, nil + } + return proxy, nil default: err := errors.New("not supported backend type") diff --git a/docs/creatingdatabases.md b/docs/creatingdatabases.md index f8c5f2c8..ce62f87c 100644 --- a/docs/creatingdatabases.md +++ b/docs/creatingdatabases.md @@ -133,6 +133,17 @@ data: ... ``` +By default ConfigMaps and Secrets are created without an Owner Reference, so they won't be removed if the `Database` resource is removed. If you want it to be deleted too, you need to turn on the cleanup function. +```YAML +apiVersion: "kci.rocks/v1alpha1" +kind: "Database" +metadata: + name: "example-db" +spec: + cleanup: true +``` + +If this feature is enabled, then `Database` becomes an owner of Secrets and ConfigMaps, and by removing a database, you'll also remove them. ### ConnectingToTheDatabase By using the secret and the configmap created by operator after database creation, pods in Kubernetes can connect to the database. diff --git a/pkg/utils/kci/decorator.go b/pkg/utils/kci/decorator.go index 2bba0b99..fba0e678 100644 --- a/pkg/utils/kci/decorator.go +++ b/pkg/utils/kci/decorator.go @@ -22,16 +22,17 @@ import ( ) // ConfigMapBuilder builds kubernetes configmap object -func ConfigMapBuilder(name string, namespace string, data map[string]string) *corev1.ConfigMap { +func ConfigMapBuilder(name string, namespace string, data map[string]string, ownership []metav1.OwnerReference) *corev1.ConfigMap { configmap := &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: BaseLabelBuilder(), + Name: name, + Namespace: namespace, + Labels: BaseLabelBuilder(), + OwnerReferences: ownership, }, Data: data, } @@ -40,16 +41,17 @@ func ConfigMapBuilder(name string, namespace string, data map[string]string) *co } // SecretBuilder builds kubernetes secret object -func SecretBuilder(secretName string, namespace string, data map[string][]byte) *corev1.Secret { +func SecretBuilder(secretName string, namespace string, data map[string][]byte, ownership []metav1.OwnerReference) *corev1.Secret { secret := &corev1.Secret{ TypeMeta: metav1.TypeMeta{ Kind: "Secret", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: namespace, - Labels: BaseLabelBuilder(), + Name: secretName, + Namespace: namespace, + Labels: BaseLabelBuilder(), + OwnerReferences: ownership, }, Data: data, } diff --git a/pkg/utils/kci/decorator_test.go b/pkg/utils/kci/decorator_test.go index 88fc43a4..b48264c0 100644 --- a/pkg/utils/kci/decorator_test.go +++ b/pkg/utils/kci/decorator_test.go @@ -26,6 +26,7 @@ import ( func TestConfigMapBuilder(t *testing.T) { name := "test-configmap" + ownership := []metav1.OwnerReference{} om := metav1.ObjectMeta{Namespace: "TestNS"} s := v1alpha1.DatabaseSpec{SecretName: "TestSec"} owner := v1alpha1.Database{ObjectMeta: om, Spec: s} @@ -33,22 +34,81 @@ func TestConfigMapBuilder(t *testing.T) { "key": "value", } - configmap := ConfigMapBuilder(name, owner.Namespace, data) + configmap := ConfigMapBuilder(name, owner.Namespace, data, ownership) assert.Equal(t, owner.Namespace, configmap.GetNamespace(), "Namespace has not expected Value") assert.Equal(t, data, configmap.Data, "Config Name not match expected Value") + assert.Equal(t, len(configmap.OwnerReferences), 0, "Unexpected size of an OwnerReference") +} + +func TestConfigMapBuilderWithOwnerReference(t *testing.T) { + name := "test-configmap" + ownership := []metav1.OwnerReference{} + ownership = append(ownership, metav1.OwnerReference{ + APIVersion: "api-version", + Kind: "kind", + Name: "name", + UID: "uid", + }) + om := metav1.ObjectMeta{Namespace: "TestNS"} + s := v1alpha1.DatabaseSpec{SecretName: "TestSec"} + owner := v1alpha1.Database{ObjectMeta: om, Spec: s} + data := map[string]string{ + "key": "value", + } + + configmap := ConfigMapBuilder(name, owner.Namespace, data, ownership) + + assert.Equal(t, owner.Namespace, configmap.GetNamespace(), "Namespace has not expected Value") + assert.Equal(t, data, configmap.Data, "Config Name not match expected Value") + assert.Equal(t, len(configmap.OwnerReferences), 1, "Unexpected size of an OwnerReference") + assert.Equal(t, configmap.OwnerReferences[0].APIVersion, ownership[0].APIVersion, "API Version in the OwnerReference is wrong") + assert.Equal(t, configmap.OwnerReferences[0].Kind, ownership[0].Kind, "Kind in the OwnerReference is wrong") + assert.Equal(t, configmap.OwnerReferences[0].Name, ownership[0].Name, "Name in the OwnerReference is wrong") + assert.Equal(t, configmap.OwnerReferences[0].UID, ownership[0].UID, "UID in the OwnerReference is wrong") } func TestSecretBuilder(t *testing.T) { name := "test-secret" o := metav1.ObjectMeta{Namespace: "TestNS"} + ownership := []metav1.OwnerReference{} + + owner := v1alpha1.Database{ObjectMeta: o} + data := map[string][]byte{ + "key": []byte("secret"), + } + + secret := SecretBuilder(name, owner.Namespace, data, ownership) + + assert.Equal(t, owner.Namespace, secret.GetNamespace(), "Namespace has not expected Value") + assert.Equal(t, data, secret.Data, "Secret Data not match expected Value") + assert.Equal(t, len(secret.OwnerReferences), 0, "Unexpected size of an OwnerReference") +} + +func TestSecretBuilderWithOwnerReference(t *testing.T) { + name := "test-secret" + o := metav1.ObjectMeta{Namespace: "TestNS"} + ownership := []metav1.OwnerReference{} + + ownership = append(ownership, metav1.OwnerReference{ + APIVersion: "api-version", + Kind: "kind", + Name: "name", + UID: "uid", + }) + owner := v1alpha1.Database{ObjectMeta: o} data := map[string][]byte{ "key": []byte("secret"), } - secret := SecretBuilder(name, owner.Namespace, data) + secret := SecretBuilder(name, owner.Namespace, data, ownership) assert.Equal(t, owner.Namespace, secret.GetNamespace(), "Namespace has not expected Value") assert.Equal(t, data, secret.Data, "Secret Data not match expected Value") + assert.Equal(t, len(secret.OwnerReferences), 1, "Unexpected size of an OwnerReference") + assert.Equal(t, secret.OwnerReferences[0].APIVersion, ownership[0].APIVersion, "API Version in the OwnerReference is wrong") + assert.Equal(t, secret.OwnerReferences[0].Kind, ownership[0].Kind, "Kind in the OwnerReference is wrong") + assert.Equal(t, secret.OwnerReferences[0].Name, ownership[0].Name, "Name in the OwnerReference is wrong") + assert.Equal(t, secret.OwnerReferences[0].UID, ownership[0].UID, "UID in the OwnerReference is wrong") } diff --git a/pkg/utils/proxy/cloudproxy.go b/pkg/utils/proxy/cloudproxy.go index e2c93c49..e1637ac4 100644 --- a/pkg/utils/proxy/cloudproxy.go +++ b/pkg/utils/proxy/cloudproxy.go @@ -43,16 +43,17 @@ type CloudProxy struct { const instanceAccessSecretVolumeName string = "gcloud-secret" -func (cp *CloudProxy) buildService() (*v1.Service, error) { +func (cp *CloudProxy) buildService(ownership []metav1.OwnerReference) (*v1.Service, error) { return &v1.Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: cp.NamePrefix + "-svc", - Namespace: cp.Namespace, - Labels: cp.Labels, + Name: cp.NamePrefix + "-svc", + Namespace: cp.Namespace, + Labels: cp.Labels, + OwnerReferences: ownership, }, Spec: v1.ServiceSpec{ Ports: []v1.ServicePort{ @@ -74,7 +75,7 @@ func (cp *CloudProxy) buildService() (*v1.Service, error) { }, nil } -func (cp *CloudProxy) buildDeployment() (*v1apps.Deployment, error) { +func (cp *CloudProxy) buildDeployment(ownership []metav1.OwnerReference) (*v1apps.Deployment, error) { spec, err := cp.deploymentSpec() if err != nil { return nil, err @@ -86,9 +87,10 @@ func (cp *CloudProxy) buildDeployment() (*v1apps.Deployment, error) { APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: cp.NamePrefix + "-cloudproxy", - Namespace: cp.Namespace, - Labels: cp.Labels, + Name: cp.NamePrefix + "-cloudproxy", + Namespace: cp.Namespace, + Labels: cp.Labels, + OwnerReferences: ownership, }, Spec: spec, }, nil @@ -185,20 +187,21 @@ func (cp *CloudProxy) container() (v1.Container, error) { }, nil } -func (cp *CloudProxy) buildConfigMap() (*v1.ConfigMap, error) { +func (cp *CloudProxy) buildConfigMap(_ []metav1.OwnerReference) (*v1.ConfigMap, error) { return nil, nil } -func (cp *CloudProxy) buildServiceMonitor() (*promv1.ServiceMonitor, error) { +func (cp *CloudProxy) buildServiceMonitor(ownership []metav1.OwnerReference) (*promv1.ServiceMonitor, error) { Endpoint := promv1.Endpoint{ Port: "metrics", } return &promv1.ServiceMonitor{ ObjectMeta: metav1.ObjectMeta{ - Name: cp.NamePrefix + "-sm", - Namespace: cp.Namespace, - Labels: cp.Labels, + Name: cp.NamePrefix + "-sm", + Namespace: cp.Namespace, + Labels: cp.Labels, + OwnerReferences: ownership, }, Spec: promv1.ServiceMonitorSpec{ Endpoints: []promv1.Endpoint{Endpoint}, diff --git a/pkg/utils/proxy/create.go b/pkg/utils/proxy/create.go index 522039b3..fe49519a 100644 --- a/pkg/utils/proxy/create.go +++ b/pkg/utils/proxy/create.go @@ -21,11 +21,12 @@ import ( "github.com/sirupsen/logrus" v1apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // BuildDeployment builds kubernetes deployment object to create proxy container of the database -func BuildDeployment(proxy Proxy) (*v1apps.Deployment, error) { - deploy, err := proxy.buildDeployment() +func BuildDeployment(proxy Proxy, ownership []metav1.OwnerReference) (*v1apps.Deployment, error) { + deploy, err := proxy.buildDeployment(ownership) if err != nil { logrus.Error("failed building proxy deployment") return nil, err @@ -35,8 +36,8 @@ func BuildDeployment(proxy Proxy) (*v1apps.Deployment, error) { } // BuildService builds kubernetes service object for proxy service of the database -func BuildService(proxy Proxy) (*v1.Service, error) { - svc, err := proxy.buildService() +func BuildService(proxy Proxy, ownership []metav1.OwnerReference) (*v1.Service, error) { + svc, err := proxy.buildService(ownership) if err != nil { logrus.Error("failed building proxy service") return nil, err @@ -46,8 +47,8 @@ func BuildService(proxy Proxy) (*v1.Service, error) { } // BuildConfigmap builds kubernetes configmap object used by proxy container of the database -func BuildConfigmap(proxy Proxy) (*v1.ConfigMap, error) { - cm, err := proxy.buildConfigMap() +func BuildConfigmap(proxy Proxy, ownership []metav1.OwnerReference) (*v1.ConfigMap, error) { + cm, err := proxy.buildConfigMap(ownership) if err != nil { logrus.Error("failed building proxy configmap") return nil, err @@ -57,8 +58,8 @@ func BuildConfigmap(proxy Proxy) (*v1.ConfigMap, error) { } // BuildServiceMonitor builds kubernetes prometheus ServiceMonitor CR object used for monitoring -func BuildServiceMonitor(proxy Proxy) (*promv1.ServiceMonitor, error) { - promSerMon, err := proxy.buildServiceMonitor() +func BuildServiceMonitor(proxy Proxy, ownership []metav1.OwnerReference) (*promv1.ServiceMonitor, error) { + promSerMon, err := proxy.buildServiceMonitor(ownership) if err != nil { logrus.Error("failed building promServiceMonitor configmap") return nil, err diff --git a/pkg/utils/proxy/types.go b/pkg/utils/proxy/types.go index b48a1439..c1db63d0 100644 --- a/pkg/utils/proxy/types.go +++ b/pkg/utils/proxy/types.go @@ -20,12 +20,13 @@ import ( promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" v1apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Proxy for database type Proxy interface { - buildDeployment() (*v1apps.Deployment, error) - buildService() (*v1.Service, error) - buildServiceMonitor() (*promv1.ServiceMonitor, error) - buildConfigMap() (*v1.ConfigMap, error) + buildDeployment(ownership []metav1.OwnerReference) (*v1apps.Deployment, error) + buildService(ownership []metav1.OwnerReference) (*v1.Service, error) + buildServiceMonitor(ownership []metav1.OwnerReference) (*promv1.ServiceMonitor, error) + buildConfigMap(ownership []metav1.OwnerReference) (*v1.ConfigMap, error) } From 39e64742d28f504d8147c5a0aa2e0e77b650fdbf Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Wed, 11 Jan 2023 15:44:39 +0100 Subject: [PATCH 2/3] Restore crs --- config/crd/bases/kci.rocks_databases.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/config/crd/bases/kci.rocks_databases.yaml b/config/crd/bases/kci.rocks_databases.yaml index 5bde21d8..9c5b14f3 100644 --- a/config/crd/bases/kci.rocks_databases.yaml +++ b/config/crd/bases/kci.rocks_databases.yaml @@ -71,8 +71,6 @@ spec: - cron - enable type: object - cleanup: - type: boolean connectionStringTemplate: description: 'ConnectionStringTemplate field can be used to pass a custom template for generating a db connection string. These keywords From be08047aeb4e2a3f55b63dc701bb4836770c10a7 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Wed, 11 Jan 2023 16:14:41 +0100 Subject: [PATCH 3/3] Remove chart files --- .../db-operator/crds/kci.rocks_databases.yaml | 327 ------------------ .../crds/kci.rocks_dbinstances.yaml | 196 ----------- 2 files changed, 523 deletions(-) delete mode 100644 charts/db-operator/crds/kci.rocks_databases.yaml delete mode 100644 charts/db-operator/crds/kci.rocks_dbinstances.yaml diff --git a/charts/db-operator/crds/kci.rocks_databases.yaml b/charts/db-operator/crds/kci.rocks_databases.yaml deleted file mode 100644 index 5bde21d8..00000000 --- a/charts/db-operator/crds/kci.rocks_databases.yaml +++ /dev/null @@ -1,327 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: databases.kci.rocks -spec: - group: kci.rocks - names: - kind: Database - listKind: DatabaseList - plural: databases - shortNames: - - db - singular: database - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: current db phase - jsonPath: .status.phase - name: Phase - type: string - - description: current db status - jsonPath: .status.status - name: Status - type: boolean - - description: If database is protected to not get deleted. - jsonPath: .spec.deletionProtected - name: Protected - type: boolean - - description: instance reference - jsonPath: .spec.instance - name: DBInstance - type: string - - description: time since creation of resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: Database is the Schema for the databases API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: DatabaseSpec defines the desired state of Database - properties: - backup: - description: DatabaseBackup defines the desired state of backup and - schedule - properties: - cron: - type: string - enable: - type: boolean - required: - - cron - - enable - type: object - cleanup: - type: boolean - connectionStringTemplate: - description: 'ConnectionStringTemplate field can be used to pass a - custom template for generating a db connection string. These keywords - can be used: Protocol, DatabaseHost, DatabasePort, UserName, Password, - DatabaseName. Default template looks like this: "{{ .Protocol }}://{{ - .UserName }}:{{ .Password }}@{{ .DatabaseHost }}:{{ .DatabasePort - }}/{{ .DatabaseName }}"' - type: string - deletionProtected: - type: boolean - extensions: - items: - type: string - type: array - instance: - type: string - postgres: - description: Postgres struct should be used to provide resource that - only applicable to postgres - properties: - dropPublicSchema: - description: If set to true, the public schema will be dropped - after the database creation - type: boolean - schemas: - description: Specify schemas to be created. The user created by - db-operator will have all access on them. - items: - type: string - type: array - type: object - secretName: - type: string - secretsTemplates: - additionalProperties: - type: string - type: object - required: - - backup - - deletionProtected - - instance - - secretName - type: object - status: - description: DatabaseStatus defines the observed state of Database - properties: - database: - type: string - instanceRef: - description: DbInstance is the Schema for the dbinstances API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: DbInstanceSpec defines the desired state of DbInstance - properties: - adminSecretRef: - description: NamespacedName is a fork of the kubernetes api - type of the same name. Sadly this is required because CRD - structs must have all fields json tagged and the kubernetes - type is not tagged. - properties: - Name: - type: string - Namespace: - type: string - required: - - Name - - Namespace - type: object - backup: - description: DbInstanceBackup defines name of google bucket - to use for storing database dumps for backup when backup - is enabled - properties: - bucket: - type: string - required: - - bucket - type: object - engine: - description: 'Important: Run "make generate" to regenerate - code after modifying this file' - type: string - generic: - description: GenericInstance is used when instance type is - generic and describes necessary informations to use instance - generic instance can be any backend, it must be reachable - by described address and port - properties: - backupHost: - description: BackupHost address will be used for dumping - database for backup Usually secondary address for primary-secondary - setup or cluster lb address If it's not defined, above - Host will be used as backup host address. - type: string - host: - type: string - port: - type: integer - publicIp: - type: string - required: - - host - - port - type: object - google: - description: GoogleInstance is used when instance type is - Google Cloud SQL and describes necessary informations to - use google API to create sql instances - properties: - apiEndpoint: - type: string - clientSecretRef: - description: NamespacedName is a fork of the kubernetes - api type of the same name. Sadly this is required because - CRD structs must have all fields json tagged and the - kubernetes type is not tagged. - properties: - Name: - type: string - Namespace: - type: string - required: - - Name - - Namespace - type: object - configmapRef: - description: NamespacedName is a fork of the kubernetes - api type of the same name. Sadly this is required because - CRD structs must have all fields json tagged and the - kubernetes type is not tagged. - properties: - Name: - type: string - Namespace: - type: string - required: - - Name - - Namespace - type: object - instance: - type: string - required: - - configmapRef - - instance - type: object - monitoring: - description: DbInstanceMonitoring defines if exporter - properties: - enabled: - type: boolean - required: - - enabled - type: object - sslConnection: - description: DbInstanceSSLConnection defines weather connection - from db-operator to instance has to be ssl or not - properties: - enabled: - type: boolean - skip-verify: - description: SkipVerity use SSL connection, but don't - check against a CA - type: boolean - required: - - enabled - - skip-verify - type: object - required: - - adminSecretRef - - engine - type: object - status: - description: DbInstanceStatus defines the observed state of DbInstance - properties: - checksums: - additionalProperties: - type: string - type: object - info: - additionalProperties: - type: string - type: object - phase: - description: 'Important: Run "make generate" to regenerate - code after modifying this file' - type: string - status: - type: boolean - required: - - phase - - status - type: object - type: object - monitorUserSecret: - type: string - phase: - description: 'Important: Run "make generate" to regenerate code after - modifying this file Add custom validation using kubebuilder tags: - https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' - type: string - proxyStatus: - description: DatabaseProxyStatus defines whether proxy for database - is enabled or not if so, provide information - properties: - serviceName: - type: string - sqlPort: - format: int32 - type: integer - status: - type: boolean - required: - - serviceName - - sqlPort - - status - type: object - status: - type: boolean - user: - type: string - required: - - database - - instanceRef - - phase - - status - - user - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/charts/db-operator/crds/kci.rocks_dbinstances.yaml b/charts/db-operator/crds/kci.rocks_dbinstances.yaml deleted file mode 100644 index 65061e90..00000000 --- a/charts/db-operator/crds/kci.rocks_dbinstances.yaml +++ /dev/null @@ -1,196 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: dbinstances.kci.rocks -spec: - group: kci.rocks - names: - kind: DbInstance - listKind: DbInstanceList - plural: dbinstances - shortNames: - - dbin - singular: dbinstance - scope: Cluster - versions: - - additionalPrinterColumns: - - description: current phase - jsonPath: .status.phase - name: Phase - type: string - - description: health status - jsonPath: .status.status - name: Status - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: DbInstance is the Schema for the dbinstances API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: DbInstanceSpec defines the desired state of DbInstance - properties: - adminSecretRef: - description: NamespacedName is a fork of the kubernetes api type of - the same name. Sadly this is required because CRD structs must have - all fields json tagged and the kubernetes type is not tagged. - properties: - Name: - type: string - Namespace: - type: string - required: - - Name - - Namespace - type: object - backup: - description: DbInstanceBackup defines name of google bucket to use - for storing database dumps for backup when backup is enabled - properties: - bucket: - type: string - required: - - bucket - type: object - engine: - description: 'Important: Run "make generate" to regenerate code after - modifying this file' - type: string - generic: - description: GenericInstance is used when instance type is generic - and describes necessary informations to use instance generic instance - can be any backend, it must be reachable by described address and - port - properties: - backupHost: - description: BackupHost address will be used for dumping database - for backup Usually secondary address for primary-secondary setup - or cluster lb address If it's not defined, above Host will be - used as backup host address. - type: string - host: - type: string - port: - type: integer - publicIp: - type: string - required: - - host - - port - type: object - google: - description: GoogleInstance is used when instance type is Google Cloud - SQL and describes necessary informations to use google API to create - sql instances - properties: - apiEndpoint: - type: string - clientSecretRef: - description: NamespacedName is a fork of the kubernetes api type - of the same name. Sadly this is required because CRD structs - must have all fields json tagged and the kubernetes type is - not tagged. - properties: - Name: - type: string - Namespace: - type: string - required: - - Name - - Namespace - type: object - configmapRef: - description: NamespacedName is a fork of the kubernetes api type - of the same name. Sadly this is required because CRD structs - must have all fields json tagged and the kubernetes type is - not tagged. - properties: - Name: - type: string - Namespace: - type: string - required: - - Name - - Namespace - type: object - instance: - type: string - required: - - configmapRef - - instance - type: object - monitoring: - description: DbInstanceMonitoring defines if exporter - properties: - enabled: - type: boolean - required: - - enabled - type: object - sslConnection: - description: DbInstanceSSLConnection defines weather connection from - db-operator to instance has to be ssl or not - properties: - enabled: - type: boolean - skip-verify: - description: SkipVerity use SSL connection, but don't check against - a CA - type: boolean - required: - - enabled - - skip-verify - type: object - required: - - adminSecretRef - - engine - type: object - status: - description: DbInstanceStatus defines the observed state of DbInstance - properties: - checksums: - additionalProperties: - type: string - type: object - info: - additionalProperties: - type: string - type: object - phase: - description: 'Important: Run "make generate" to regenerate code after - modifying this file' - type: string - status: - type: boolean - required: - - phase - - status - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: []