Skip to content

Commit 077cf9d

Browse files
authored
Add an ability to remove secrets and configmaps when database is gone (#168)
1 parent 7250ff5 commit 077cf9d

File tree

15 files changed

+230
-104
lines changed

15 files changed

+230
-104
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ TMP_DIR=$$(mktemp -d) ;\
8585
cd $$TMP_DIR ;\
8686
go mod init tmp ;\
8787
echo "Downloading $(2)" ;\
88-
GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\
88+
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
8989
rm -rf $$TMP_DIR ;\
9090
}
9191
endef

api/v1alpha1/database_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type DatabaseSpec struct {
3838
ConnectionStringTemplate string `json:"connectionStringTemplate,omitempty"`
3939
SecretsTemplates map[string]string `json:"secretsTemplates,omitempty"`
4040
Postgres Postgres `json:"postgres,omitempty"`
41+
Cleanup bool `json:"cleanup,omitempty"`
4142
}
4243

4344
// Postgres struct should be used to provide resource that only applicable to postgres

controllers/backup/cronjob.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import (
3434
// GCSBackupCron builds kubernetes cronjob object
3535
// to create database backup regularly with defined schedule from dbcr
3636
// this job will database dump and upload to google bucket storage for backup
37-
func GCSBackupCron(conf *config.Config, dbcr *kciv1alpha1.Database) (*batchv1beta1.CronJob, error) {
37+
func GCSBackupCron(conf *config.Config, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) (*batchv1beta1.CronJob, error) {
3838
cronJobSpec, err := buildCronJobSpec(conf, dbcr)
3939
if err != nil {
4040
return nil, err
@@ -46,9 +46,10 @@ func GCSBackupCron(conf *config.Config, dbcr *kciv1alpha1.Database) (*batchv1bet
4646
APIVersion: "batch",
4747
},
4848
ObjectMeta: metav1.ObjectMeta{
49-
Name: dbcr.Namespace + "-" + dbcr.Name + "-" + "backup",
50-
Namespace: dbcr.Namespace,
51-
Labels: kci.BaseLabelBuilder(),
49+
Name: dbcr.Namespace + "-" + dbcr.Name + "-" + "backup",
50+
Namespace: dbcr.Namespace,
51+
Labels: kci.BaseLabelBuilder(),
52+
OwnerReferences: ownership,
5253
},
5354
Spec: cronJobSpec,
5455
}, nil

controllers/backup/cronjob_test.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import (
2626
"github.com/stretchr/testify/assert"
2727
v1 "k8s.io/api/core/v1"
2828
"k8s.io/apimachinery/pkg/api/resource"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930
)
3031

3132
func TestGCSBackupCronGsql(t *testing.T) {
33+
ownership := []metav1.OwnerReference{}
3234
dbcr := &kciv1alpha1.Database{}
3335
dbcr.Namespace = "TestNS"
3436
dbcr.Name = "TestDB"
@@ -43,15 +45,15 @@ func TestGCSBackupCronGsql(t *testing.T) {
4345
conf := config.LoadConfig()
4446

4547
instance.Spec.Engine = "postgres"
46-
funcCronObject, err := GCSBackupCron(&conf, dbcr)
48+
funcCronObject, err := GCSBackupCron(&conf, dbcr, ownership)
4749
if err != nil {
4850
fmt.Print(err)
4951
}
5052

5153
assert.Equal(t, "postgresbackupimage:latest", funcCronObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image)
5254

5355
instance.Spec.Engine = "mysql"
54-
funcCronObject, err = GCSBackupCron(&conf, dbcr)
56+
funcCronObject, err = GCSBackupCron(&conf, dbcr, ownership)
5557
if err != nil {
5658
fmt.Print(err)
5759
}
@@ -64,6 +66,7 @@ func TestGCSBackupCronGsql(t *testing.T) {
6466
}
6567

6668
func TestGCSBackupCronGeneric(t *testing.T) {
69+
ownership := []metav1.OwnerReference{}
6770
dbcr := &kciv1alpha1.Database{}
6871
dbcr.Namespace = "TestNS"
6972
dbcr.Name = "TestDB"
@@ -78,15 +81,15 @@ func TestGCSBackupCronGeneric(t *testing.T) {
7881
conf := config.LoadConfig()
7982

8083
instance.Spec.Engine = "postgres"
81-
funcCronObject, err := GCSBackupCron(&conf, dbcr)
84+
funcCronObject, err := GCSBackupCron(&conf, dbcr, ownership)
8285
if err != nil {
8386
fmt.Print(err)
8487
}
8588

8689
assert.Equal(t, "postgresbackupimage:latest", funcCronObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image)
8790

8891
instance.Spec.Engine = "mysql"
89-
funcCronObject, err = GCSBackupCron(&conf, dbcr)
92+
funcCronObject, err = GCSBackupCron(&conf, dbcr, ownership)
9093
if err != nil {
9194
fmt.Print(err)
9295
}
@@ -96,8 +99,42 @@ func TestGCSBackupCronGeneric(t *testing.T) {
9699
assert.Equal(t, "TestNS", funcCronObject.Namespace)
97100
assert.Equal(t, "TestNS-TestDB-backup", funcCronObject.Name)
98101
assert.Equal(t, "* * * * *", funcCronObject.Spec.Schedule)
102+
assert.Equal(t, len(funcCronObject.OwnerReferences), 0, "Unexpected size of an OwnerReference")
99103
}
100104

105+
func TestGCSBackupCronGenericWithOwnerReference(t *testing.T) {
106+
ownership := []metav1.OwnerReference{}
107+
ownership = append(ownership, metav1.OwnerReference{
108+
APIVersion: "api-version",
109+
Kind: "kind",
110+
Name: "name",
111+
UID: "uid",
112+
})
113+
dbcr := &kciv1alpha1.Database{}
114+
dbcr.Namespace = "TestNS"
115+
dbcr.Name = "TestDB"
116+
instance := &kciv1alpha1.DbInstance{}
117+
instance.Status.Info = map[string]string{"DB_CONN": "TestConnection", "DB_PORT": "1234"}
118+
instance.Spec.Generic = &kciv1alpha1.GenericInstance{BackupHost: "slave.test"}
119+
dbcr.Status.InstanceRef = instance
120+
dbcr.Spec.Instance = "staging"
121+
dbcr.Spec.Backup.Cron = "* * * * *"
122+
123+
os.Setenv("CONFIG_PATH", "./test/backup_config.yaml")
124+
conf := config.LoadConfig()
125+
126+
instance.Spec.Engine = "postgres"
127+
funcCronObject, err := GCSBackupCron(&conf, dbcr, ownership)
128+
if err != nil {
129+
fmt.Print(err)
130+
}
131+
132+
assert.Equal(t, len(funcCronObject.OwnerReferences), 1, "Unexpected size of an OwnerReference")
133+
assert.Equal(t, funcCronObject.OwnerReferences[0].APIVersion, ownership[0].APIVersion, "API Version in the OwnerReference is wrong")
134+
assert.Equal(t, funcCronObject.OwnerReferences[0].Kind, ownership[0].Kind, "Kind in the OwnerReference is wrong")
135+
assert.Equal(t, funcCronObject.OwnerReferences[0].Name, ownership[0].Name, "Name in the OwnerReference is wrong")
136+
assert.Equal(t, funcCronObject.OwnerReferences[0].UID, ownership[0].UID, "UID in the OwnerReference is wrong")
137+
}
101138
func TestGetResourceRequirements(t *testing.T) {
102139
os.Setenv("CONFIG_PATH", "./test/backup_config.yaml")
103140
conf := config.LoadConfig()

controllers/database_controller.go

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -179,37 +179,47 @@ func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
179179

180180
// database status not true, process phase
181181
if !dbcr.Status.Status {
182+
ownership := []metav1.OwnerReference{}
183+
if dbcr.Spec.Cleanup {
184+
ownership = append(ownership, metav1.OwnerReference{
185+
APIVersion: dbcr.APIVersion,
186+
Kind: dbcr.Kind,
187+
Name: dbcr.Name,
188+
UID: dbcr.GetUID(),
189+
},
190+
)
191+
}
192+
182193
phase := dbcr.Status.Phase
183194
logrus.Infof("DB: namespace=%s, name=%s start %s", dbcr.Namespace, dbcr.Name, phase)
184195

185196
defer promDBsPhaseTime.WithLabelValues(phase).Observe(kci.TimeTrack(time.Now()))
186-
err := r.createDatabase(ctx, dbcr)
197+
err := r.createDatabase(ctx, dbcr, ownership)
187198
if err != nil {
188199
// when database creation failed, don't requeue request. to prevent exceeding api limit (ex: against google api)
189200
return r.manageError(ctx, dbcr, err, false)
190201
}
202+
191203
dbcr.Status.Phase = dbPhaseInstanceAccessSecret
192-
err = r.createInstanceAccessSecret(ctx, dbcr)
193-
if err != nil {
204+
205+
if err = r.createInstanceAccessSecret(ctx, dbcr, ownership); err != nil {
194206
return r.manageError(ctx, dbcr, err, true)
195207
}
196208
dbcr.Status.Phase = dbPhaseProxy
197-
err = r.createProxy(ctx, dbcr)
209+
err = r.createProxy(ctx, dbcr, ownership)
198210
if err != nil {
199211
return r.manageError(ctx, dbcr, err, true)
200212
}
201213
dbcr.Status.Phase = dbPhaseSecretsTemplating
202-
err = r.createTemplatedSecrets(ctx, dbcr)
203-
if err != nil {
214+
if err = r.createTemplatedSecrets(ctx, dbcr, ownership); err != nil {
204215
return r.manageError(ctx, dbcr, err, true)
205216
}
206217
dbcr.Status.Phase = dbPhaseConfigMap
207-
err = r.createInfoConfigMap(ctx, dbcr)
208-
if err != nil {
218+
if err = r.createInfoConfigMap(ctx, dbcr, ownership); err != nil {
209219
return r.manageError(ctx, dbcr, err, true)
210220
}
211221
dbcr.Status.Phase = dbPhaseBackupJob
212-
err = r.createBackupJob(ctx, dbcr)
222+
err = r.createBackupJob(ctx, dbcr, ownership)
213223
if err != nil {
214224
return r.manageError(ctx, dbcr, err, true)
215225
}
@@ -278,7 +288,7 @@ func (r *DatabaseReconciler) initialize(ctx context.Context, dbcr *kciv1alpha1.D
278288
}
279289

280290
// createDatabase secret, actual database using admin secret
281-
func (r *DatabaseReconciler) createDatabase(ctx context.Context, dbcr *kciv1alpha1.Database) error {
291+
func (r *DatabaseReconciler) createDatabase(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error {
282292
databaseSecret, err := r.getDatabaseSecret(ctx, dbcr)
283293
if err != nil {
284294
if k8serrors.IsNotFound(err) {
@@ -287,7 +297,7 @@ func (r *DatabaseReconciler) createDatabase(ctx context.Context, dbcr *kciv1alph
287297
logrus.Errorf("can not generate credentials for database - %s", err)
288298
return err
289299
}
290-
newDatabaseSecret := kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.Namespace, secretData)
300+
newDatabaseSecret := kci.SecretBuilder(dbcr.Spec.SecretName, dbcr.Namespace, secretData, ownership)
291301
err = r.Create(ctx, newDatabaseSecret)
292302
if err != nil {
293303
// failed to create secret
@@ -388,7 +398,7 @@ func (r *DatabaseReconciler) deleteDatabase(ctx context.Context, dbcr *kciv1alph
388398
return nil
389399
}
390400

391-
func (r *DatabaseReconciler) createInstanceAccessSecret(ctx context.Context, dbcr *kciv1alpha1.Database) error {
401+
func (r *DatabaseReconciler) createInstanceAccessSecret(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error {
392402
if backend, _ := dbcr.GetBackendType(); backend != "google" {
393403
logrus.Debugf("DB: namespace=%s, name=%s %s doesn't need instance access secret skipping...", dbcr.Namespace, dbcr.Name, backend)
394404
return nil
@@ -422,7 +432,7 @@ func (r *DatabaseReconciler) createInstanceAccessSecret(ctx context.Context, dbc
422432
secretData[credFile] = data
423433

424434
newName := dbcr.InstanceAccessSecretName()
425-
newSecret := kci.SecretBuilder(newName, dbcr.GetNamespace(), secretData)
435+
newSecret := kci.SecretBuilder(newName, dbcr.GetNamespace(), secretData, ownership)
426436

427437
err = r.Create(ctx, newSecret)
428438
if err != nil {
@@ -442,7 +452,7 @@ func (r *DatabaseReconciler) createInstanceAccessSecret(ctx context.Context, dbc
442452
return nil
443453
}
444454

445-
func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1.Database) error {
455+
func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error {
446456
backend, _ := dbcr.GetBackendType()
447457
if backend == "generic" {
448458
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.
455465
}
456466

457467
// create proxy configmap
458-
cm, err := proxy.BuildConfigmap(proxyInterface)
468+
cm, err := proxy.BuildConfigmap(proxyInterface, ownership)
459469
if err != nil {
460470
return err
461471
}
@@ -478,7 +488,7 @@ func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1.
478488
}
479489

480490
// create proxy deployment
481-
deploy, err := proxy.BuildDeployment(proxyInterface)
491+
deploy, err := proxy.BuildDeployment(proxyInterface, ownership)
482492
if err != nil {
483493
return err
484494
}
@@ -499,7 +509,7 @@ func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1.
499509
}
500510

501511
// create proxy service
502-
svc, err := proxy.BuildService(proxyInterface)
512+
svc, err := proxy.BuildService(proxyInterface, ownership)
503513
if err != nil {
504514
return err
505515
}
@@ -533,7 +543,7 @@ func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1.
533543

534544
if isMonitoringEnabled && inCrdList(crdList, "servicemonitors.monitoring.coreos.com") {
535545
// create proxy PromServiceMonitor
536-
promSvcMon, err := proxy.BuildServiceMonitor(proxyInterface)
546+
promSvcMon, err := proxy.BuildServiceMonitor(proxyInterface, ownership)
537547
if err != nil {
538548
return err
539549
}
@@ -567,15 +577,17 @@ func (r *DatabaseReconciler) createProxy(ctx context.Context, dbcr *kciv1alpha1.
567577
return nil
568578
}
569579

570-
func (r *DatabaseReconciler) createTemplatedSecrets(ctx context.Context, dbcr *kciv1alpha1.Database) error {
580+
func (r *DatabaseReconciler) createTemplatedSecrets(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error {
571581
// First of all the password should be taken from secret because it's not stored anywhere else
572582
databaseSecret, err := r.getDatabaseSecret(ctx, dbcr)
573583
if err != nil {
574584
return err
575585
}
576586
// Then parse the secret to get the password
577-
// Connection stirng is deprecated and will be removed soon. So this switch is temporary.
587+
// Connection string is deprecated and will be removed soon. So this switch is temporary.
578588
// Once connection string is removed, the switch and the following if condition are gone
589+
// Connection String doesn't support the cleaning up feature, so the secret with a connection
590+
// string won't be removed after a db resource is removed.
579591
useLegacyConnectionString := false
580592
switch {
581593
case len(dbcr.Spec.ConnectionStringTemplate) > 0 && len(dbcr.Spec.SecretsTemplates) > 0:
@@ -610,29 +622,31 @@ func (r *DatabaseReconciler) createTemplatedSecrets(ctx context.Context, dbcr *k
610622
}
611623
logrus.Debugf("DB: namespace=%s, name=%s updating credentials secret", dbcr.Namespace, dbcr.Name)
612624
newSecret := addConnectionStringToSecret(dbcr, databaseSecret.Data, dbConnectionString)
613-
return r.Update(ctx, newSecret, &client.UpdateOptions{})
625+
if err = r.Update(ctx, newSecret, &client.UpdateOptions{}); err != nil {
626+
return err
627+
}
628+
return nil
614629
}
615630

616631
dbSecrets, err := generateTemplatedSecrets(dbcr, databaseCred)
617632
if err != nil {
618633
return err
619634
}
620635
// Adding values
621-
newSecret := fillTemplatedSecretData(dbcr, databaseSecret.Data, dbSecrets)
622-
err = r.Update(ctx, newSecret, &client.UpdateOptions{})
623-
if err != nil {
636+
newSecret := fillTemplatedSecretData(dbcr, databaseSecret.Data, dbSecrets, ownership)
637+
if err = r.Update(ctx, newSecret, &client.UpdateOptions{}); err != nil {
624638
return err
625639
}
626-
newSecret = removeObsoleteSecret(dbcr, databaseSecret.Data, dbSecrets)
627-
err = r.Update(ctx, newSecret, &client.UpdateOptions{})
628-
if err != nil {
640+
641+
newSecret = removeObsoleteSecret(dbcr, databaseSecret.Data, dbSecrets, ownership)
642+
if err = r.Update(ctx, newSecret, &client.UpdateOptions{}); err != nil {
629643
return err
630644
}
631645

632646
return nil
633647
}
634648

635-
func (r *DatabaseReconciler) createInfoConfigMap(ctx context.Context, dbcr *kciv1alpha1.Database) error {
649+
func (r *DatabaseReconciler) createInfoConfigMap(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error {
636650
instance, err := dbcr.GetInstanceRef()
637651
if err != nil {
638652
return err
@@ -645,19 +659,7 @@ func (r *DatabaseReconciler) createInfoConfigMap(ctx context.Context, dbcr *kciv
645659
info["DB_HOST"] = proxyStatus.ServiceName
646660
info["DB_PORT"] = strconv.FormatInt(int64(proxyStatus.SQLPort), 10)
647661
}
648-
649-
databaseConfigResource := &corev1.ConfigMap{
650-
TypeMeta: metav1.TypeMeta{
651-
Kind: "ConfigMap",
652-
APIVersion: "v1",
653-
},
654-
ObjectMeta: metav1.ObjectMeta{
655-
Namespace: dbcr.Namespace,
656-
Name: dbcr.Spec.SecretName,
657-
Labels: kci.BaseLabelBuilder(),
658-
},
659-
Data: info,
660-
}
662+
databaseConfigResource := kci.ConfigMapBuilder(dbcr.Spec.SecretName, dbcr.Namespace, info, ownership)
661663

662664
err = r.Create(ctx, databaseConfigResource)
663665
if err != nil {
@@ -678,13 +680,13 @@ func (r *DatabaseReconciler) createInfoConfigMap(ctx context.Context, dbcr *kciv
678680
return nil
679681
}
680682

681-
func (r *DatabaseReconciler) createBackupJob(ctx context.Context, dbcr *kciv1alpha1.Database) error {
683+
func (r *DatabaseReconciler) createBackupJob(ctx context.Context, dbcr *kciv1alpha1.Database, ownership []metav1.OwnerReference) error {
682684
if !dbcr.Spec.Backup.Enable {
683685
// if not enabled, skip
684686
return nil
685687
}
686688

687-
cronjob, err := backup.GCSBackupCron(r.Conf, dbcr)
689+
cronjob, err := backup.GCSBackupCron(r.Conf, dbcr, ownership)
688690
if err != nil {
689691
return err
690692
}

0 commit comments

Comments
 (0)