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

zalando / postgres-operator / 20954401287

13 Jan 2026 11:04AM UTC coverage: 43.491% (-0.009%) from 43.5%
20954401287

Pull #3031

github

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

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

72 existing lines in 2 files now uncovered.

6555 of 15072 relevant lines covered (43.49%)

16.45 hits per line

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

59.56
/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 {
×
UNCOV
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,
×
UNCOV
35
                PodServiceAccount:   c.PodServiceAccount,
×
UNCOV
36
        }
×
37
}
38

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

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

2✔
47
        if c.curWorkerID == c.opConfig.Workers-1 {
2✔
UNCOV
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 {
×
UNCOV
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
×
UNCOV
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 {
×
UNCOV
90
                        case apiextv1.Established:
×
UNCOV
91
                                if cond.Status == apiextv1.ConditionTrue {
×
UNCOV
92
                                        return true, err
×
93
                                }
×
UNCOV
94
                        case apiextv1.NamesAccepted:
×
UNCOV
95
                                if cond.Status == apiextv1.ConditionFalse {
×
UNCOV
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 {
×
106
        crd, err := acidv1.PostgresCRD(c.opConfig.CRDCategories)
×
107
        if err != nil {
×
UNCOV
108
                return fmt.Errorf("could not create Postgres CRD object: %v", err)
×
UNCOV
109
        }
×
UNCOV
110
        return c.createOperatorCRD(crd)
×
111
}
112

113
func (c *Controller) createConfigurationCRD() error {
×
UNCOV
114
        return c.createOperatorCRD(acidv1.ConfigurationCRD(c.opConfig.CRDCategories))
×
UNCOV
115
}
×
116

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

125
var emptyName = (spec.NamespacedName{})
126

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

8✔
134
        // take from CRD configuration
8✔
135
        rolesDefs := c.opConfig.InfrastructureRoles
8✔
136

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

5✔
143
                var secretName spec.NamespacedName
5✔
144
                var err error
5✔
145
                propertySep := ","
5✔
146
                valueSep := ":"
5✔
147

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

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

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

183
                if roleDef.SecretName != emptyName &&
5✔
184
                        (roleDef.UserKey != "" || roleDef.DefaultUserValue != "") &&
5✔
185
                        roleDef.PasswordKey != "" {
8✔
186
                        rolesDefs = append(rolesDefs, &roleDef)
3✔
187
                }
3✔
188
        }
189

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

203
        return rolesDefs
8✔
204
}
205

206
func (c *Controller) getInfrastructureRoles(
207
        rolesSecrets []*config.InfrastructureRole) (
208
        map[string]spec.PgUser, error) {
5✔
209

5✔
210
        errors := make([]string, 0)
5✔
211
        noRolesProvided := true
5✔
212
        roles := []spec.PgUser{}
5✔
213
        uniqRoles := make(map[string]spec.PgUser)
5✔
214

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

225
        if noRolesProvided {
6✔
226
                return uniqRoles, nil
1✔
227
        }
1✔
228

229
        for _, secret := range rolesSecrets {
9✔
230
                infraRoles, err := c.getInfrastructureRole(secret)
5✔
231

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

1✔
235
                        if err != nil {
2✔
236
                                errors = append(errors, fmt.Sprintf("%v", err))
1✔
237
                        }
1✔
238

239
                        continue
1✔
240
                }
241

242
                roles = append(roles, infraRoles...)
4✔
243
        }
244

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

251
                uniqRoles[r.Name] = r
6✔
252
        }
253

254
        if len(errors) > 0 {
5✔
255
                return uniqRoles, fmt.Errorf("%s", strings.Join(errors, `', '`))
1✔
256
        }
1✔
257

258
        return uniqRoles, nil
3✔
259
}
260

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

5✔
279
        rolesSecret := infraRole.SecretName
5✔
280
        roles := []spec.PgUser{}
5✔
281

5✔
282
        if rolesSecret == emptyName {
5✔
UNCOV
283
                // we don't have infrastructure roles defined, bail out
×
UNCOV
284
                return nil, nil
×
UNCOV
285
        }
×
286

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

295
        secretData := infraRolesSecret.Data
4✔
296

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

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

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

354
                if !roleDescr.Valid() {
3✔
355
                        msg := "infrastructure role %q is not complete and ignored"
×
356
                        c.logger.Warningf(msg, roleDescr)
×
357

×
UNCOV
358
                        return nil, nil
×
UNCOV
359
                }
×
360

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

367
                if roleDescr.Password == "" {
3✔
UNCOV
368
                        msg := "infrastructure role %q has no password defined and is ignored"
×
UNCOV
369
                        c.logger.Warningf(msg, roleDescr.Name)
×
UNCOV
370
                        return nil, nil
×
UNCOV
371
                }
×
372

373
                roles = append(roles, *roleDescr)
3✔
374
        }
375

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

403
        // TODO: check for role collisions
404
        return roles, nil
4✔
405
}
406

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

413
        c.pgTeamMap.Load(pgTeams)
×
414
        c.logger.Debugf("Internal Postgres Team Cache: %#v", c.pgTeamMap)
×
415
}
416

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

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

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

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