@@ -8,7 +8,7 @@ package v1
8
8
9
9
import (
10
10
"fmt"
11
- "time "
11
+ "reflect "
12
12
13
13
"regexp"
14
14
@@ -18,6 +18,7 @@ import (
18
18
corev1 "k8s.io/api/core/v1"
19
19
networkingv1 "k8s.io/api/networking/v1"
20
20
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
21
22
"k8s.io/apimachinery/pkg/runtime"
22
23
"k8s.io/apimachinery/pkg/types"
23
24
"k8s.io/apimachinery/pkg/util/intstr"
@@ -28,57 +29,67 @@ import (
28
29
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
29
30
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
30
31
32
+ const (
33
+ // UIDLabelName Name of the label referencing the owning Postgres resource in the control cluster
34
+ UIDLabelName string = "postgres.database.fits.cloud/uuid"
35
+ // TenantLabelName Name of the tenant label
36
+ TenantLabelName string = "postgres.database.fits.cloud/tenant"
37
+ // ProjectIDLabelName Name of the ProjectID label
38
+ ProjectIDLabelName string = "postgres.database.fits.cloud/project-id"
39
+ // ManagedByLabelName Name of the managed-by label
40
+ ManagedByLabelName string = "postgres.database.fits.cloud/managed-by"
41
+ // ManagedByLabelValue Value of the managed-by label
42
+ ManagedByLabelValue string = "postgreslet"
43
+ // PostgresFinalizerName Name of the finalizer to use
44
+ PostgresFinalizerName string = "postgres.finalizers.database.fits.cloud"
45
+ // CreatedByAnnotationKey is used to store who in person created this database
46
+ CreatedByAnnotationKey string = "postgres.database.fits.cloud/created-by"
47
+ // BackupConfigLabelName if set to true, this secret stores the backupConfig
48
+ BackupConfigLabelName string = "postgres.database.fits.cloud/is-backup"
49
+ // BackupConfigKey defines the key under which the BackupConfig is stored in the data map.
50
+ BackupConfigKey = "config"
51
+ )
52
+
53
+ // BackupConfig defines all properties to configure backup of a database.
54
+ // This config is stored in the data section under the key BackupConfigKey as json payload.
55
+ type BackupConfig struct {
56
+ // ID of this backupConfig
57
+ ID string `json:"id"`
58
+ // Name is a user defined description
59
+ Name string `json:"name"`
60
+ // ProjectID the project this backup is mapped to
61
+ ProjectID string `json:"project"`
62
+ // Tenant the tenant of the backup
63
+ Tenant string `json:"tenant"`
64
+ // Retention defines how many versions should be held in s3
65
+ Retention string `json:"retention"`
66
+ // Schedule in cron syntax when to run the backup periodically
67
+ Schedule string `json:"schedule"`
68
+
69
+ // S3Endpoint the url of the s3 endpoint
70
+ S3Endpoint string `json:"s3endpoint"`
71
+ // S3BucketName is the name of the bucket where the backup should be stored.
72
+ S3BucketName string `json:"s3bucketname"`
73
+ // S3Region the region of the aws s3
74
+ S3Region string `json:"s3region"`
75
+ // S3AccessKey is the accesskey which must have write access
76
+ S3AccessKey string `json:"s3accesskey"`
77
+ // S3SecretKey is the secretkey which must match to the accesskey
78
+ S3SecretKey string `json:"s3secretkey"`
79
+ }
80
+
81
+ var ZalandoPostgresqlTypeMeta = metav1.TypeMeta {
82
+ APIVersion : "acid.zalan.do/v1" ,
83
+ Kind : "postgresql" ,
84
+ }
85
+
31
86
// +kubebuilder:object:root=true
32
87
// +kubebuilder:subresource:status
33
88
// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.version`
34
89
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.description`
35
90
// +kubebuilder:printcolumn:name="Load-Balancer-IP",type=string,JSONPath=`.status.socket.ip`
36
91
// +kubebuilder:printcolumn:name="Load-Balancer-Port",type=integer,JSONPath=`.status.socket.port`
37
92
38
- // UIDLabelName Name of the label referencing the owning Postgres resource in the control cluster
39
- const UIDLabelName string = "postgres.database.fits.cloud/uuid"
40
-
41
- // TenantLabelName Name of the tenant label
42
- const TenantLabelName string = "postgres.database.fits.cloud/tenant"
43
-
44
- // ProjectIDLabelName Name of the ProjectID label
45
- const ProjectIDLabelName string = "postgres.database.fits.cloud/project-id"
46
-
47
- // ManagedByLabelName Name of the managed-by label
48
- const ManagedByLabelName string = "postgres.database.fits.cloud/managed-by"
49
-
50
- // ManagedByLabelValue Value of the managed-by label
51
- const ManagedByLabelValue string = "postgreslet"
52
-
53
- // PostgresFinalizerName Name of the finalizer to use
54
- const PostgresFinalizerName string = "postgres.finalizers.database.fits.cloud"
55
-
56
- // Backup configure parametes of the database backup
57
- const (
58
- // S3URL defines the s3 endpoint URL for backup
59
- BackupSecretS3Endpoint = "s3Endpoint"
60
- // S3BucketName defines the name of the S3 bucket for backup
61
- BackupSecretS3BucketName = "s3BucketName"
62
- // Retention defines how many days a backup will persist
63
- BackupSecretRetention = "retention"
64
- // Schedule defines how often a backup should be made, in cron format
65
- BackupSecretSchedule = "schedule"
66
- BackupSecretAccessKey = "accesskey"
67
- BackupSecretSecretKey = "secretkey"
68
- BackupSecretProjectKey = "project"
69
- )
70
-
71
- const (
72
- Sun Weekday = iota
73
- Mon
74
- Tue
75
- Wed
76
- Thu
77
- Fri
78
- Sat
79
- All
80
- )
81
-
82
93
// Postgres is the Schema for the postgres API
83
94
type Postgres struct {
84
95
metav1.TypeMeta `json:",inline"`
@@ -117,7 +128,7 @@ type PostgresSpec struct {
117
128
118
129
// todo: add default
119
130
// Maintenance defines automatic maintenance of the database
120
- Maintenance * Maintenance `json:"maintenance,omitempty"`
131
+ Maintenance [] string `json:"maintenance,omitempty"`
121
132
122
133
// AccessList defines access restrictions
123
134
AccessList * AccessList `json:"accessList,omitempty"`
@@ -132,6 +143,7 @@ type AccessList struct {
132
143
SourceRanges []string `json:"sourceRanges,omitempty"`
133
144
}
134
145
146
+ // Todo: Add defaults
135
147
// Size defines the size aspects of the database
136
148
type Size struct {
137
149
// CPU is in the format as pod.spec.resource.request.cpu
@@ -147,23 +159,6 @@ type Size struct {
147
159
StorageSize string `json:"storageSize,omitempty"`
148
160
}
149
161
150
- // Weekday defines a weekday or everyday
151
- type Weekday int
152
-
153
- // TimeWindow defines an interval in time
154
- type TimeWindow struct {
155
- Start metav1.Time `json:"start,omitempty"`
156
- End metav1.Time `json:"end,omitempty"`
157
- }
158
-
159
- // Maintenance configures database maintenance
160
- type Maintenance struct {
161
- // Weekday defines when the operator is allowed to do maintenance
162
- Weekday Weekday `json:"weekday,omitempty"`
163
- // TimeWindow defines when the maintenance should happen
164
- TimeWindow TimeWindow `json:"timeWindow,omitempty"`
165
- }
166
-
167
162
// PostgresStatus defines the observed state of Postgres
168
163
type PostgresStatus struct {
169
164
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
@@ -307,7 +302,7 @@ func (p *Postgres) ToPeripheralResourceName() string {
307
302
return p .generateTeamID () + "-" + p .generateDatabaseName ()
308
303
}
309
304
310
- // ToUserPasswordSecret returns the secret containing user password pairs
305
+ // ToUserPasswordsSecret returns the secret containing user password pairs
311
306
func (p * Postgres ) ToUserPasswordsSecret (src * corev1.SecretList , scheme * runtime.Scheme ) (* corev1.Secret , error ) {
312
307
secret := & corev1.Secret {}
313
308
secret .Namespace = p .Namespace
@@ -384,99 +379,63 @@ func (p *Postgres) ToPeripheralResourceNamespace() string {
384
379
return p .ToPeripheralResourceName ()
385
380
}
386
381
387
- func (p * Postgres ) ToZalandoPostgres () * ZalandoPostgres {
388
- return & ZalandoPostgres {
389
- TypeMeta : ZalandoPostgresTypeMeta ,
390
- ObjectMeta : metav1.ObjectMeta {
391
- Name : p .ToPeripheralResourceName (),
392
- Namespace : p .ToPeripheralResourceNamespace (),
393
- Labels : map [string ]string {UIDLabelName : string (p .UID )},
394
- },
395
- Spec : ZalandoPostgresSpec {
396
- MaintenanceWindows : func () []MaintenanceWindow {
397
- if p .Spec .Maintenance == nil {
398
- return nil
399
- }
400
- isEvery := p .Spec .Maintenance .Weekday == All
401
- return []MaintenanceWindow {
402
- {Everyday : isEvery ,
403
- Weekday : func () time.Weekday {
404
- if isEvery {
405
- return time .Weekday (0 )
406
- }
407
- return time .Weekday (p .Spec .Maintenance .Weekday )
408
- }(),
409
- StartTime : p .Spec .Maintenance .TimeWindow .Start ,
410
- EndTime : p .Spec .Maintenance .TimeWindow .End ,
411
- },
412
- }
413
- }(),
414
- NumberOfInstances : p .Spec .NumberOfInstances ,
415
- PostgresqlParam : PostgresqlParam {PgVersion : p .Spec .Version },
416
- Resources : func () * Resources {
417
- if p .Spec .Size .CPU == "" {
418
- return nil
419
- }
420
- return & Resources {
421
- ResourceRequests : & ResourceDescription {
422
- CPU : p .Spec .Size .CPU ,
423
- Memory : p .Spec .Size .Memory ,
424
- },
425
- ResourceLimits : & ResourceDescription {
426
- CPU : p .Spec .Size .CPU ,
427
- Memory : p .Spec .Size .Memory ,
428
- },
429
- }
430
- }(),
431
- TeamID : p .generateTeamID (),
432
- Volume : Volume {Size : p .Spec .Size .StorageSize },
433
- },
382
+ func (p * Postgres ) ToUnstructuredZalandoPostgresql (z * zalando.Postgresql ) (* unstructured.Unstructured , error ) {
383
+ if z == nil {
384
+ z = & zalando.Postgresql {}
434
385
}
435
- }
386
+ z .TypeMeta = ZalandoPostgresqlTypeMeta
387
+ z .Namespace = p .ToPeripheralResourceNamespace ()
388
+ z .Name = p .ToPeripheralResourceName ()
389
+ z .Labels = p .ToZalandoPostgresqlMatchingLabels ()
390
+
391
+ z .Spec .NumberOfInstances = p .Spec .NumberOfInstances
392
+ z .Spec .PostgresqlParam .PgVersion = p .Spec .Version
393
+ z .Spec .Resources .ResourceRequests .CPU = p .Spec .Size .CPU
394
+ z .Spec .Resources .ResourceRequests .Memory = p .Spec .Size .Memory
395
+ z .Spec .Resources .ResourceLimits .CPU = p .Spec .Size .CPU
396
+ z .Spec .Resources .ResourceLimits .Memory = p .Spec .Size .Memory
397
+ z .Spec .TeamID = p .generateTeamID ()
398
+ z .Spec .Volume .Size = p .Spec .Size .StorageSize
436
399
437
- func (p * Postgres ) ToPatchedZalandoPostgresql (z * zalando.Postgresql ) * zalando.Postgresql {
438
400
if p .HasSourceRanges () {
439
401
z .Spec .AllowedSourceRanges = p .Spec .AccessList .SourceRanges
440
402
}
441
403
442
- z .Spec .NumberOfInstances = p .Spec .NumberOfInstances
443
-
444
- // todo: Check if the validation should be performed here.
445
- z .Spec .PostgresqlParam .PgVersion = p .Spec .Version
404
+ jsonZ , err := runtime .DefaultUnstructuredConverter .ToUnstructured (z )
405
+ if err != nil {
406
+ return nil , fmt .Errorf ("failed to convert to unstructured zalando postgresql: %w" , err )
407
+ }
408
+ jsonSpec , _ := jsonZ ["spec" ].(map [string ]interface {})
446
409
447
- z .Spec .ResourceRequests .CPU = p .Spec .Size .CPU
448
- z .Spec .ResourceRequests .Memory = p .Spec .Size .Memory
449
- z .Spec .ResourceLimits .CPU = p .Spec .Size .CPU
450
- z .Spec .ResourceLimits .Memory = p .Spec .Size .Memory
410
+ // In the code, zalando's `MaintenanceWindows` is a `struct`, but in the CRD
411
+ // it's an array of strings, so we can only set its `Unstructured` contents
412
+ // and deal with possible `nil`.
413
+ jsonSpec ["maintenanceWindows" ] = p .Spec .Maintenance
414
+ deleteIfEmpty (jsonSpec , "maintenanceWindows" )
451
415
452
- // todo: Check if the validation should be performed here.
453
- z .Spec .Volume .Size = p .Spec .Size .StorageSize
416
+ // Delete unused fields
417
+ deleteIfEmpty (jsonSpec , "clone" )
418
+ deleteIfEmpty (jsonSpec , "patroni" ) // if in use, deleteIfEmpty needs to consider the case of struct.
419
+ deleteIfEmpty (jsonSpec , "podAnnotations" )
420
+ deleteIfEmpty (jsonSpec , "serviceAnnotations" )
421
+ deleteIfEmpty (jsonSpec , "standby" )
422
+ deleteIfEmpty (jsonSpec , "tls" )
423
+ deleteIfEmpty (jsonSpec , "users" )
454
424
455
- z .Spec .MaintenanceWindows = func () []zalando.MaintenanceWindow {
456
- if p .Spec .Maintenance == nil {
457
- return nil
458
- }
459
- isEvery := p .Spec .Maintenance .Weekday == All
460
- return []zalando.MaintenanceWindow {
461
- {
462
- Everyday : isEvery ,
463
- Weekday : func () time.Weekday {
464
- if isEvery {
465
- return time .Weekday (0 )
466
- }
467
- return time .Weekday (p .Spec .Maintenance .Weekday )
468
- }(),
469
- StartTime : p .Spec .Maintenance .TimeWindow .Start ,
470
- EndTime : p .Spec .Maintenance .TimeWindow .End ,
471
- },
472
- }
473
- }()
425
+ jsonP , _ := jsonSpec ["postgresql" ].(map [string ]interface {})
426
+ deleteIfEmpty (jsonP , "parameters" )
474
427
475
- return z
428
+ return & unstructured.Unstructured {
429
+ Object : jsonZ ,
430
+ }, nil
476
431
}
477
432
478
433
func (p * Postgres ) ToZalandoPostgresqlMatchingLabels () client.MatchingLabels {
479
- return client.MatchingLabels {UIDLabelName : string (p .UID )}
434
+ return client.MatchingLabels {
435
+ ProjectIDLabelName : p .Spec .PartitionID ,
436
+ TenantLabelName : p .Spec .Tenant ,
437
+ UIDLabelName : string (p .UID ),
438
+ }
480
439
}
481
440
482
441
func (p * Postgres ) HasFinalizer (finalizerName string ) bool {
@@ -510,6 +469,15 @@ func removeElem(ss []string, s string) (out []string) {
510
469
return
511
470
}
512
471
472
+ func deleteIfEmpty (json map [string ]interface {}, key string ) {
473
+ i := json [key ]
474
+
475
+ // interface has type and value. The chained function calls deal with nil value with type.
476
+ if i == nil || reflect .ValueOf (json [key ]).IsNil () {
477
+ delete (json , key )
478
+ }
479
+ }
480
+
513
481
func init () {
514
482
SchemeBuilder .Register (& Postgres {}, & PostgresList {})
515
483
}
0 commit comments