• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zalando / postgres-operator / 19932871056

04 Dec 2025 02:42PM UTC coverage: 43.398%. First build
19932871056

Pull #3007

github

web-flow
Merge c6f78f8e4 into 3cec0d38e
Pull Request #3007: Generate postgresql CRD from go structs

1 of 5 new or added lines in 2 files covered. (20.0%)

6524 of 15033 relevant lines covered (43.4%)

15.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.13
/pkg/controller/util.go
1
package controller
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "fmt"
7
        "strings"
8

9
        v1 "k8s.io/api/core/v1"
10
        apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
11
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12
        "k8s.io/apimachinery/pkg/types"
13
        "k8s.io/apimachinery/pkg/util/wait"
14

15
        acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
16
        "github.com/zalando/postgres-operator/pkg/cluster"
17
        "github.com/zalando/postgres-operator/pkg/spec"
18
        "github.com/zalando/postgres-operator/pkg/util"
19
        "github.com/zalando/postgres-operator/pkg/util/config"
20
        "github.com/zalando/postgres-operator/pkg/util/k8sutil"
21
        "gopkg.in/yaml.v2"
22
)
23

24
func (c *Controller) makeClusterConfig() cluster.Config {
×
25
        infrastructureRoles := make(map[string]spec.PgUser)
×
26
        for k, v := range c.config.InfrastructureRoles {
×
27
                infrastructureRoles[k] = v
×
28
        }
×
29

30
        return cluster.Config{
×
31
                RestConfig:          c.config.RestConfig,
×
32
                OpConfig:            config.Copy(c.opConfig),
×
33
                PgTeamMap:           &c.pgTeamMap,
×
34
                InfrastructureRoles: infrastructureRoles,
×
35
                PodServiceAccount:   c.PodServiceAccount,
×
36
        }
×
37
}
38

39
func (c *Controller) clusterWorkerID(clusterName spec.NamespacedName) uint32 {
2✔
40
        workerID, ok := c.clusterWorkers[clusterName]
2✔
41
        if ok {
2✔
42
                return workerID
×
43
        }
×
44

45
        c.clusterWorkers[clusterName] = c.curWorkerID
2✔
46

2✔
47
        if c.curWorkerID == c.opConfig.Workers-1 {
2✔
48
                c.curWorkerID = 0
×
49
        } else {
2✔
50
                c.curWorkerID++
2✔
51
        }
2✔
52

53
        return c.clusterWorkers[clusterName]
2✔
54
}
55

56
func (c *Controller) createOperatorCRD(desiredCrd *apiextv1.CustomResourceDefinition) error {
×
57
        crd, err := c.KubeClient.CustomResourceDefinitions().Get(context.TODO(), desiredCrd.Name, metav1.GetOptions{})
×
58
        if k8sutil.ResourceNotFound(err) {
×
59
                if _, err := c.KubeClient.CustomResourceDefinitions().Create(context.TODO(), desiredCrd, metav1.CreateOptions{}); err != nil {
×
60
                        return fmt.Errorf("could not create customResourceDefinition %q: %v", desiredCrd.Name, err)
×
61
                }
×
62
        }
63
        if err != nil {
×
64
                c.logger.Errorf("could not get customResourceDefinition %q: %v", desiredCrd.Name, err)
×
65
        }
×
66
        if crd != nil {
×
67
                c.logger.Infof("customResourceDefinition %q is already registered and will only be updated", crd.Name)
×
68
                // copy annotations and labels from existing CRD since we do not define them
×
69
                desiredCrd.Annotations = crd.Annotations
×
70
                desiredCrd.Labels = crd.Labels
×
71
                patch, err := json.Marshal(desiredCrd)
×
72
                if err != nil {
×
73
                        return fmt.Errorf("could not marshal new customResourceDefintion %q: %v", desiredCrd.Name, err)
×
74
                }
×
75
                if _, err := c.KubeClient.CustomResourceDefinitions().Patch(
×
76
                        context.TODO(), crd.Name, types.MergePatchType, patch, metav1.PatchOptions{}); err != nil {
×
77
                        return fmt.Errorf("could not update customResourceDefinition %q: %v", crd.Name, err)
×
78
                }
×
79
        }
80
        c.logger.Infof("customResourceDefinition %q is registered", crd.Name)
×
81

×
82
        return wait.PollUntilContextTimeout(context.TODO(), c.config.CRDReadyWaitInterval, c.config.CRDReadyWaitTimeout, false, func(ctx context.Context) (bool, error) {
×
83
                c, err := c.KubeClient.CustomResourceDefinitions().Get(context.TODO(), desiredCrd.Name, metav1.GetOptions{})
×
84
                if err != nil {
×
85
                        return false, err
×
86
                }
×
87

88
                for _, cond := range c.Status.Conditions {
×
89
                        switch cond.Type {
×
90
                        case apiextv1.Established:
×
91
                                if cond.Status == apiextv1.ConditionTrue {
×
92
                                        return true, err
×
93
                                }
×
94
                        case apiextv1.NamesAccepted:
×
95
                                if cond.Status == apiextv1.ConditionFalse {
×
96
                                        return false, fmt.Errorf("name conflict: %v", cond.Reason)
×
97
                                }
×
98
                        }
99
                }
100

101
                return false, err
×
102
        })
103
}
104

105
func (c *Controller) createPostgresCRD() error {
×
NEW
106
        return c.createOperatorCRD(acidv1.PostgresCRD(c.opConfig.CRDCategories))
×
NEW
107
}
×
108

NEW
109
func (c *Controller) createConfigurationCRD() error {
×
NEW
110
        return c.createOperatorCRD(acidv1.ConfigurationCRD(c.opConfig.CRDCategories))
×
111
}
×
112

113
func readDecodedRole(s string) (*spec.PgUser, error) {
2✔
114
        var result spec.PgUser
2✔
115
        if err := yaml.Unmarshal([]byte(s), &result); err != nil {
2✔
116
                return nil, fmt.Errorf("could not decode yaml role: %v", err)
×
117
        }
×
118
        return &result, nil
2✔
119
}
120

121
var emptyName = (spec.NamespacedName{})
122

123
// Return information about what secrets we need to use to create
124
// infrastructure roles and in which format are they. This is done in
125
// compatible way, so that the previous logic is not changed, and handles both
126
// configuration in ConfigMap & CRD.
127
func (c *Controller) getInfrastructureRoleDefinitions() []*config.InfrastructureRole {
8✔
128
        var roleDef config.InfrastructureRole
8✔
129

8✔
130
        // take from CRD configuration
8✔
131
        rolesDefs := c.opConfig.InfrastructureRoles
8✔
132

8✔
133
        // check if we can extract something from the configmap config option
8✔
134
        if c.opConfig.InfrastructureRolesDefs != "" {
13✔
135
                // The configmap option could contain either a role description (in the
5✔
136
                // form key1: value1, key2: value2), which has to be used together with
5✔
137
                // an old secret name.
5✔
138

5✔
139
                var secretName spec.NamespacedName
5✔
140
                var err error
5✔
141
                propertySep := ","
5✔
142
                valueSep := ":"
5✔
143

5✔
144
                // The field contains the format in which secret is written, let's
5✔
145
                // convert it to a proper definition
5✔
146
                properties := strings.Split(c.opConfig.InfrastructureRolesDefs, propertySep)
5✔
147
                roleDef = config.InfrastructureRole{Template: false}
5✔
148

5✔
149
                for _, property := range properties {
21✔
150
                        values := strings.Split(property, valueSep)
16✔
151
                        if len(values) < 2 {
17✔
152
                                continue
1✔
153
                        }
154
                        name := strings.TrimSpace(values[0])
15✔
155
                        value := strings.TrimSpace(values[1])
15✔
156

15✔
157
                        switch name {
15✔
158
                        case "secretname":
3✔
159
                                if err = secretName.DecodeWorker(value, "default"); err != nil {
3✔
160
                                        c.logger.Warningf("Could not marshal secret name %s: %v", value, err)
×
161
                                } else {
3✔
162
                                        roleDef.SecretName = secretName
3✔
163
                                }
3✔
164
                        case "userkey":
4✔
165
                                roleDef.UserKey = value
4✔
166
                        case "passwordkey":
4✔
167
                                roleDef.PasswordKey = value
4✔
168
                        case "rolekey":
3✔
169
                                roleDef.RoleKey = value
3✔
170
                        case "defaultuservalue":
×
171
                                roleDef.DefaultUserValue = value
×
172
                        case "defaultrolevalue":
1✔
173
                                roleDef.DefaultRoleValue = value
1✔
174
                        default:
×
175
                                c.logger.Warningf("Role description is not known: %s", properties)
×
176
                        }
177
                }
178

179
                if roleDef.SecretName != emptyName &&
5✔
180
                        (roleDef.UserKey != "" || roleDef.DefaultUserValue != "") &&
5✔
181
                        roleDef.PasswordKey != "" {
8✔
182
                        rolesDefs = append(rolesDefs, &roleDef)
3✔
183
                }
3✔
184
        }
185

186
        if c.opConfig.InfrastructureRolesSecretName != emptyName {
11✔
187
                // At this point we deal with the old format, let's replicate it
3✔
188
                // via existing definition structure and remember that it's just a
3✔
189
                // template, the real values are in user1,password1,inrole1 etc.
3✔
190
                rolesDefs = append(rolesDefs, &config.InfrastructureRole{
3✔
191
                        SecretName:  c.opConfig.InfrastructureRolesSecretName,
3✔
192
                        UserKey:     "user",
3✔
193
                        PasswordKey: "password",
3✔
194
                        RoleKey:     "inrole",
3✔
195
                        Template:    true,
3✔
196
                })
3✔
197
        }
3✔
198

199
        return rolesDefs
8✔
200
}
201

202
func (c *Controller) getInfrastructureRoles(
203
        rolesSecrets []*config.InfrastructureRole) (
204
        map[string]spec.PgUser, error) {
5✔
205

5✔
206
        errors := make([]string, 0)
5✔
207
        noRolesProvided := true
5✔
208
        roles := []spec.PgUser{}
5✔
209
        uniqRoles := make(map[string]spec.PgUser)
5✔
210

5✔
211
        // To be compatible with the legacy implementation we need to return nil if
5✔
212
        // the provided secret name is empty. The equivalent situation in the
5✔
213
        // current implementation is an empty rolesSecrets slice or all its items
5✔
214
        // are empty.
5✔
215
        for _, role := range rolesSecrets {
11✔
216
                if role.SecretName != emptyName {
11✔
217
                        noRolesProvided = false
5✔
218
                }
5✔
219
        }
220

221
        if noRolesProvided {
6✔
222
                return uniqRoles, nil
1✔
223
        }
1✔
224

225
        for _, secret := range rolesSecrets {
9✔
226
                infraRoles, err := c.getInfrastructureRole(secret)
5✔
227

5✔
228
                if err != nil || infraRoles == nil {
6✔
229
                        c.logger.Debugf("cannot get infrastructure role: %+v", *secret)
1✔
230

1✔
231
                        if err != nil {
2✔
232
                                errors = append(errors, fmt.Sprintf("%v", err))
1✔
233
                        }
1✔
234

235
                        continue
1✔
236
                }
237

238
                roles = append(roles, infraRoles...)
4✔
239
        }
240

241
        for _, r := range roles {
10✔
242
                if _, exists := uniqRoles[r.Name]; exists {
6✔
243
                        msg := "conflicting infrastructure roles: roles[%s] = (%q, %q)"
×
244
                        c.logger.Debugf(msg, r.Name, uniqRoles[r.Name], r)
×
245
                }
×
246

247
                uniqRoles[r.Name] = r
6✔
248
        }
249

250
        if len(errors) > 0 {
5✔
251
                return uniqRoles, fmt.Errorf("%s", strings.Join(errors, `', '`))
1✔
252
        }
1✔
253

254
        return uniqRoles, nil
3✔
255
}
256

257
// Generate list of users representing one infrastructure role based on its
258
// description in various K8S objects. An infrastructure role could be
259
// described by a secret and optionally a config map. The former should contain
260
// the secret information, i.e. username, password, role. The latter could
261
// contain an extensive description of the role and even override an
262
// information obtained from the secret (except a password).
263
//
264
// This function returns a list of users to be compatible with the previous
265
// behaviour, since we don't know how many users are actually encoded in the
266
// secret if it's a "template" role. If the provided role is not a template
267
// one, the result would be a list with just one user in it.
268
//
269
// FIXME: This dependency on two different objects is rather unnecessary
270
// complicated, so let's get rid of it via deprecation process.
271
func (c *Controller) getInfrastructureRole(
272
        infraRole *config.InfrastructureRole) (
273
        []spec.PgUser, error) {
5✔
274

5✔
275
        rolesSecret := infraRole.SecretName
5✔
276
        roles := []spec.PgUser{}
5✔
277

5✔
278
        if rolesSecret == emptyName {
5✔
279
                // we don't have infrastructure roles defined, bail out
×
280
                return nil, nil
×
281
        }
×
282

283
        infraRolesSecret, err := c.KubeClient.
5✔
284
                Secrets(rolesSecret.Namespace).
5✔
285
                Get(context.TODO(), rolesSecret.Name, metav1.GetOptions{})
5✔
286
        if err != nil {
6✔
287
                msg := "could not get infrastructure roles secret %s/%s: %v"
1✔
288
                return nil, fmt.Errorf(msg, rolesSecret.Namespace, rolesSecret.Name, err)
1✔
289
        }
1✔
290

291
        secretData := infraRolesSecret.Data
4✔
292

4✔
293
        if infraRole.Template {
5✔
294
        Users:
1✔
295
                for i := 1; i <= len(secretData); i++ {
2✔
296
                        properties := []string{
1✔
297
                                infraRole.UserKey,
1✔
298
                                infraRole.PasswordKey,
1✔
299
                                infraRole.RoleKey,
1✔
300
                        }
1✔
301
                        t := spec.PgUser{Origin: spec.RoleOriginInfrastructure}
1✔
302
                        for _, p := range properties {
4✔
303
                                key := fmt.Sprintf("%s%d", p, i)
3✔
304
                                if val, present := secretData[key]; !present {
3✔
305
                                        if p == "user" {
×
306
                                                // exit when the user name with the next sequence id is
×
307
                                                // absent
×
308
                                                break Users
×
309
                                        }
310
                                } else {
3✔
311
                                        s := string(val)
3✔
312
                                        switch p {
3✔
313
                                        case "user":
1✔
314
                                                t.Name = s
1✔
315
                                        case "password":
1✔
316
                                                t.Password = s
1✔
317
                                        case "inrole":
1✔
318
                                                t.MemberOf = append(t.MemberOf, s)
1✔
319
                                        default:
×
320
                                                c.logger.Warningf("unknown key %q", p)
×
321
                                        }
322
                                }
323
                                // XXX: This is a part of the original implementation, which is
324
                                // rather obscure. Why do we delete this key? Wouldn't it be
325
                                // used later in comparison for configmap?
326
                                delete(secretData, key)
3✔
327
                        }
328

329
                        if t.Valid() {
2✔
330
                                roles = append(roles, t)
1✔
331
                        } else {
1✔
332
                                msg := "infrastructure role %q is not complete and ignored"
×
333
                                c.logger.Warningf(msg, t)
×
334
                        }
×
335
                }
336
        } else {
3✔
337
                roleDescr := &spec.PgUser{Origin: spec.RoleOriginInfrastructure}
3✔
338

3✔
339
                if details, exists := secretData[infraRole.Details]; exists {
3✔
340
                        if err := yaml.Unmarshal(details, &roleDescr); err != nil {
×
341
                                return nil, fmt.Errorf("could not decode yaml role: %v", err)
×
342
                        }
×
343
                } else {
3✔
344
                        roleDescr.Name = util.Coalesce(string(secretData[infraRole.UserKey]), infraRole.DefaultUserValue)
3✔
345
                        roleDescr.Password = string(secretData[infraRole.PasswordKey])
3✔
346
                        roleDescr.MemberOf = append(roleDescr.MemberOf,
3✔
347
                                util.Coalesce(string(secretData[infraRole.RoleKey]), infraRole.DefaultRoleValue))
3✔
348
                }
3✔
349

350
                if !roleDescr.Valid() {
3✔
351
                        msg := "infrastructure role %q is not complete and ignored"
×
352
                        c.logger.Warningf(msg, roleDescr)
×
353

×
354
                        return nil, nil
×
355
                }
×
356

357
                if roleDescr.Name == "" {
3✔
358
                        msg := "infrastructure role %q has no name defined and is ignored"
×
359
                        c.logger.Warningf(msg, roleDescr.Name)
×
360
                        return nil, nil
×
361
                }
×
362

363
                if roleDescr.Password == "" {
3✔
364
                        msg := "infrastructure role %q has no password defined and is ignored"
×
365
                        c.logger.Warningf(msg, roleDescr.Name)
×
366
                        return nil, nil
×
367
                }
×
368

369
                roles = append(roles, *roleDescr)
3✔
370
        }
371

372
        // Now plot twist. We need to check if there is a configmap with the same
373
        // name and extract a role description if it exists.
374
        infraRolesMap, err := c.KubeClient.
4✔
375
                ConfigMaps(rolesSecret.Namespace).
4✔
376
                Get(context.TODO(), rolesSecret.Name, metav1.GetOptions{})
4✔
377
        if err == nil {
6✔
378
                // we have a configmap with username - json description, let's read and decode it
2✔
379
                for role, s := range infraRolesMap.Data {
4✔
380
                        roleDescr, err := readDecodedRole(s)
2✔
381
                        if err != nil {
2✔
382
                                return nil, fmt.Errorf("could not decode role description: %v", err)
×
383
                        }
×
384
                        // check if we have a a password in a configmap
385
                        c.logger.Debugf("found role description for role %q: %+v", role, roleDescr)
2✔
386
                        if passwd, ok := secretData[role]; ok {
4✔
387
                                roleDescr.Password = string(passwd)
2✔
388
                                delete(secretData, role)
2✔
389
                        } else {
2✔
390
                                c.logger.Warningf("infrastructure role %q has no password defined and is ignored", role)
×
391
                                continue
×
392
                        }
393
                        roleDescr.Name = role
2✔
394
                        roleDescr.Origin = spec.RoleOriginInfrastructure
2✔
395
                        roles = append(roles, *roleDescr)
2✔
396
                }
397
        }
398

399
        // TODO: check for role collisions
400
        return roles, nil
4✔
401
}
402

403
func (c *Controller) loadPostgresTeams() {
×
404
        pgTeams, err := c.KubeClient.PostgresTeamsGetter.PostgresTeams(c.opConfig.WatchedNamespace).List(context.TODO(), metav1.ListOptions{})
×
405
        if err != nil {
×
406
                c.logger.Errorf("could not list postgres team objects: %v", err)
×
407
        }
×
408

409
        c.pgTeamMap.Load(pgTeams)
×
410
        c.logger.Debugf("Internal Postgres Team Cache: %#v", c.pgTeamMap)
×
411
}
412

413
func (c *Controller) postgresTeamAdd(obj interface{}) {
×
414
        pgTeam, ok := obj.(*acidv1.PostgresTeam)
×
415
        if !ok {
×
416
                c.logger.Errorf("could not cast to PostgresTeam spec")
×
417
                return
×
418
        }
×
419
        c.logger.Debugf("PostgreTeam %q added. Reloading postgres team CRDs and overwriting cached map", pgTeam.Name)
×
420
        c.loadPostgresTeams()
×
421
}
422

423
func (c *Controller) postgresTeamUpdate(prev, obj interface{}) {
×
424
        pgTeam, ok := obj.(*acidv1.PostgresTeam)
×
425
        if !ok {
×
426
                c.logger.Errorf("could not cast to PostgresTeam spec")
×
427
                return
×
428
        }
×
429
        c.logger.Debugf("PostgreTeam %q updated. Reloading postgres team CRDs and overwriting cached map", pgTeam.Name)
×
430
        c.loadPostgresTeams()
×
431
}
432

433
func (c *Controller) podClusterName(pod *v1.Pod) spec.NamespacedName {
2✔
434
        if name, ok := pod.Labels[c.opConfig.ClusterNameLabel]; ok {
3✔
435
                return spec.NamespacedName{
1✔
436
                        Namespace: pod.Namespace,
1✔
437
                        Name:      name,
1✔
438
                }
1✔
439
        }
1✔
440

441
        return spec.NamespacedName{}
1✔
442
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc