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

zalando / postgres-operator / 20956215418

13 Jan 2026 12:10PM UTC coverage: 43.511% (+0.01%) from 43.5%
20956215418

Pull #3031

github

web-flow
Merge f0b112e2a into 97115d6e3
Pull Request #3031: Debug serving CRD at runtime

0 of 1 new or added line in 1 file covered. (0.0%)

49 existing lines in 2 files now uncovered.

6555 of 15065 relevant lines covered (43.51%)

16.46 hits per line

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

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

3
import (
4
        "context"
5
        "fmt"
6
        "strings"
7

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

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

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

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

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

43
        c.clusterWorkers[clusterName] = c.curWorkerID
2✔
44

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

51
        return c.clusterWorkers[clusterName]
2✔
52
}
53

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

×
76
        return wait.PollUntilContextTimeout(context.TODO(), c.config.CRDReadyWaitInterval, c.config.CRDReadyWaitTimeout, false, func(ctx context.Context) (bool, error) {
×
77
                c, err := c.KubeClient.CustomResourceDefinitions().Get(context.TODO(), desiredCrd.Name, metav1.GetOptions{})
×
78
                if err != nil {
×
UNCOV
79
                        return false, err
×
80
                }
×
81

82
                for _, cond := range c.Status.Conditions {
×
83
                        switch cond.Type {
×
84
                        case apiextv1.Established:
×
85
                                if cond.Status == apiextv1.ConditionTrue {
×
86
                                        return true, err
×
87
                                }
×
88
                        case apiextv1.NamesAccepted:
×
89
                                if cond.Status == apiextv1.ConditionFalse {
×
UNCOV
90
                                        return false, fmt.Errorf("name conflict: %v", cond.Reason)
×
UNCOV
91
                                }
×
92
                        }
93
                }
94

UNCOV
95
                return false, err
×
96
        })
97
}
98

99
func (c *Controller) createPostgresCRD() error {
×
100
        crd, err := acidv1.PostgresCRD(c.opConfig.CRDCategories)
×
101
        if err != nil {
×
102
                return fmt.Errorf("could not create Postgres CRD object: %v", err)
×
UNCOV
103
        }
×
UNCOV
104
        return c.createOperatorCRD(crd)
×
105
}
106

107
func (c *Controller) createConfigurationCRD() error {
×
UNCOV
108
        return c.createOperatorCRD(acidv1.ConfigurationCRD(c.opConfig.CRDCategories))
×
UNCOV
109
}
×
110

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

119
var emptyName = (spec.NamespacedName{})
120

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

8✔
128
        // take from CRD configuration
8✔
129
        rolesDefs := c.opConfig.InfrastructureRoles
8✔
130

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

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

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

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

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

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

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

197
        return rolesDefs
8✔
198
}
199

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

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

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

219
        if noRolesProvided {
6✔
220
                return uniqRoles, nil
1✔
221
        }
1✔
222

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

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

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

233
                        continue
1✔
234
                }
235

236
                roles = append(roles, infraRoles...)
4✔
237
        }
238

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

245
                uniqRoles[r.Name] = r
6✔
246
        }
247

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

252
        return uniqRoles, nil
3✔
253
}
254

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

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

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

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

289
        secretData := infraRolesSecret.Data
4✔
290

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

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

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

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

×
UNCOV
352
                        return nil, nil
×
UNCOV
353
                }
×
354

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

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

367
                roles = append(roles, *roleDescr)
3✔
368
        }
369

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

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

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

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

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

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

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

439
        return spec.NamespacedName{}
1✔
440
}
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