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

zalando / postgres-operator / 20955739126

13 Jan 2026 11:53AM UTC coverage: 43.509% (+0.009%) from 43.5%
20955739126

Pull #3031

github

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

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

57 existing lines in 2 files now uncovered.

6555 of 15066 relevant lines covered (43.51%)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

198
        return rolesDefs
8✔
199
}
200

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

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

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

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

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

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

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

234
                        continue
1✔
235
                }
236

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

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

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

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

253
        return uniqRoles, nil
3✔
254
}
255

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

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

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

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

290
        secretData := infraRolesSecret.Data
4✔
291

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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