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

zalando / postgres-operator / 17042710574

18 Aug 2025 01:55PM UTC coverage: 42.062% (-3.4%) from 45.498%
17042710574

Pull #2943

github

web-flow
Merge 045132513 into 51135b07d
Pull Request #2943: Ensure compatibility with Spilo after WAL-E removal (support old and new versions)

14 of 15 new or added lines in 3 files covered. (93.33%)

531 existing lines in 13 files now uncovered.

6494 of 15439 relevant lines covered (42.06%)

15.22 hits per line

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

0.0
/pkg/util/k8sutil/k8sutil.go
1
package k8sutil
2

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

7
        b64 "encoding/base64"
8
        "encoding/json"
9

10
        apiacidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
11
        zalandoclient "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned"
12
        acidv1 "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
13
        zalandov1 "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/zalando.org/v1"
14
        "github.com/zalando/postgres-operator/pkg/spec"
15
        apiappsv1 "k8s.io/api/apps/v1"
16
        v1 "k8s.io/api/core/v1"
17
        apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
18
        apiextclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
19
        apiextv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
20
        apierrors "k8s.io/apimachinery/pkg/api/errors"
21
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22
        "k8s.io/apimachinery/pkg/types"
23
        "k8s.io/client-go/kubernetes"
24
        appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
25
        batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
26
        corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
27
        policyv1 "k8s.io/client-go/kubernetes/typed/policy/v1"
28
        rbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
29
        "k8s.io/client-go/rest"
30
        "k8s.io/client-go/tools/clientcmd"
31
)
32

UNCOV
33
func Int32ToPointer(value int32) *int32 {
×
UNCOV
34
        return &value
×
UNCOV
35
}
×
36

UNCOV
37
func UInt32ToPointer(value uint32) *uint32 {
×
UNCOV
38
        return &value
×
UNCOV
39
}
×
40

UNCOV
41
func StringToPointer(str string) *string {
×
UNCOV
42
        return &str
×
UNCOV
43
}
×
44

45
// KubernetesClient describes getters for Kubernetes objects
46
type KubernetesClient struct {
47
        corev1.SecretsGetter
48
        corev1.ServicesGetter
49
        corev1.EndpointsGetter
50
        corev1.PodsGetter
51
        corev1.PersistentVolumesGetter
52
        corev1.PersistentVolumeClaimsGetter
53
        corev1.ConfigMapsGetter
54
        corev1.NodesGetter
55
        corev1.NamespacesGetter
56
        corev1.ServiceAccountsGetter
57
        corev1.EventsGetter
58
        appsv1.StatefulSetsGetter
59
        appsv1.DeploymentsGetter
60
        rbacv1.RoleBindingsGetter
61
        batchv1.CronJobsGetter
62
        policyv1.PodDisruptionBudgetsGetter
63
        apiextv1client.CustomResourceDefinitionsGetter
64
        acidv1.OperatorConfigurationsGetter
65
        acidv1.PostgresTeamsGetter
66
        acidv1.PostgresqlsGetter
67
        zalandov1.FabricEventStreamsGetter
68

69
        RESTClient         rest.Interface
70
        AcidV1ClientSet    *zalandoclient.Clientset
71
        Zalandov1ClientSet *zalandoclient.Clientset
72
}
73

74
type mockCustomResourceDefinition struct {
75
        apiextv1client.CustomResourceDefinitionInterface
76
}
77

78
type MockCustomResourceDefinitionsGetter struct {
79
}
80

81
type mockSecret struct {
82
        corev1.SecretInterface
83
}
84

85
type MockSecretGetter struct {
86
}
87

88
type mockDeployment struct {
89
        appsv1.DeploymentInterface
90
}
91

92
type mockDeploymentNotExist struct {
93
        appsv1.DeploymentInterface
94
}
95

96
type MockDeploymentGetter struct {
97
}
98

99
type MockDeploymentNotExistGetter struct {
100
}
101

102
type mockService struct {
103
        corev1.ServiceInterface
104
}
105

106
type mockServiceNotExist struct {
107
        corev1.ServiceInterface
108
}
109

110
type MockServiceGetter struct {
111
}
112

113
type MockServiceNotExistGetter struct {
114
}
115

116
type mockConfigMap struct {
117
        corev1.ConfigMapInterface
118
}
119

120
type MockConfigMapsGetter struct {
121
}
122

123
// RestConfig creates REST config
124
func RestConfig(kubeConfig string, outOfCluster bool) (*rest.Config, error) {
×
125
        if outOfCluster {
×
126
                return clientcmd.BuildConfigFromFlags("", kubeConfig)
×
127
        }
×
128

129
        return rest.InClusterConfig()
×
130
}
131

132
// ResourceAlreadyExists checks if error corresponds to Already exists error
UNCOV
133
func ResourceAlreadyExists(err error) bool {
×
UNCOV
134
        return apierrors.IsAlreadyExists(err)
×
UNCOV
135
}
×
136

137
// ResourceNotFound checks if error corresponds to Not found error
UNCOV
138
func ResourceNotFound(err error) bool {
×
UNCOV
139
        return apierrors.IsNotFound(err)
×
UNCOV
140
}
×
141

142
// NewFromConfig create Kubernetes Interface using REST config
143
func NewFromConfig(cfg *rest.Config) (KubernetesClient, error) {
×
144
        kubeClient := KubernetesClient{}
×
145

×
146
        client, err := kubernetes.NewForConfig(cfg)
×
147
        if err != nil {
×
148
                return kubeClient, fmt.Errorf("could not get clientset: %v", err)
×
149
        }
×
150

151
        kubeClient.PodsGetter = client.CoreV1()
×
152
        kubeClient.ServicesGetter = client.CoreV1()
×
153
        kubeClient.EndpointsGetter = client.CoreV1()
×
154
        kubeClient.SecretsGetter = client.CoreV1()
×
155
        kubeClient.ServiceAccountsGetter = client.CoreV1()
×
156
        kubeClient.ConfigMapsGetter = client.CoreV1()
×
157
        kubeClient.PersistentVolumeClaimsGetter = client.CoreV1()
×
158
        kubeClient.PersistentVolumesGetter = client.CoreV1()
×
159
        kubeClient.NodesGetter = client.CoreV1()
×
160
        kubeClient.NamespacesGetter = client.CoreV1()
×
161
        kubeClient.StatefulSetsGetter = client.AppsV1()
×
162
        kubeClient.DeploymentsGetter = client.AppsV1()
×
163
        kubeClient.PodDisruptionBudgetsGetter = client.PolicyV1()
×
164
        kubeClient.RESTClient = client.CoreV1().RESTClient()
×
165
        kubeClient.RoleBindingsGetter = client.RbacV1()
×
166
        kubeClient.CronJobsGetter = client.BatchV1()
×
167
        kubeClient.EventsGetter = client.CoreV1()
×
168

×
169
        apiextClient, err := apiextclient.NewForConfig(cfg)
×
170
        if err != nil {
×
171
                return kubeClient, fmt.Errorf("could not create api client:%v", err)
×
172
        }
×
173

174
        kubeClient.CustomResourceDefinitionsGetter = apiextClient.ApiextensionsV1()
×
175

×
176
        kubeClient.AcidV1ClientSet = zalandoclient.NewForConfigOrDie(cfg)
×
177
        if err != nil {
×
178
                return kubeClient, fmt.Errorf("could not create acid.zalan.do clientset: %v", err)
×
179
        }
×
180
        kubeClient.Zalandov1ClientSet = zalandoclient.NewForConfigOrDie(cfg)
×
181
        if err != nil {
×
182
                return kubeClient, fmt.Errorf("could not create zalando.org clientset: %v", err)
×
183
        }
×
184

185
        kubeClient.OperatorConfigurationsGetter = kubeClient.AcidV1ClientSet.AcidV1()
×
186
        kubeClient.PostgresTeamsGetter = kubeClient.AcidV1ClientSet.AcidV1()
×
187
        kubeClient.PostgresqlsGetter = kubeClient.AcidV1ClientSet.AcidV1()
×
188
        kubeClient.FabricEventStreamsGetter = kubeClient.Zalandov1ClientSet.ZalandoV1()
×
189

×
190
        return kubeClient, nil
×
191
}
192

193
// SetPostgresCRDStatus of Postgres cluster
UNCOV
194
func (client *KubernetesClient) SetPostgresCRDStatus(clusterName spec.NamespacedName, status string) (*apiacidv1.Postgresql, error) {
×
UNCOV
195
        var pg *apiacidv1.Postgresql
×
UNCOV
196
        var pgStatus apiacidv1.PostgresStatus
×
UNCOV
197
        pgStatus.PostgresClusterStatus = status
×
UNCOV
198

×
UNCOV
199
        patch, err := json.Marshal(struct {
×
UNCOV
200
                PgStatus interface{} `json:"status"`
×
UNCOV
201
        }{&pgStatus})
×
UNCOV
202

×
UNCOV
203
        if err != nil {
×
204
                return pg, fmt.Errorf("could not marshal status: %v", err)
×
205
        }
×
206

207
        // we cannot do a full scale update here without fetching the previous manifest (as the resourceVersion may differ),
208
        // however, we could do patch without it. In the future, once /status subresource is there (starting Kubernetes 1.11)
209
        // we should take advantage of it.
UNCOV
210
        pg, err = client.PostgresqlsGetter.Postgresqls(clusterName.Namespace).Patch(
×
UNCOV
211
                context.TODO(), clusterName.Name, types.MergePatchType, patch, metav1.PatchOptions{}, "status")
×
UNCOV
212
        if err != nil {
×
UNCOV
213
                return pg, fmt.Errorf("could not update status: %v", err)
×
UNCOV
214
        }
×
215

UNCOV
216
        return pg, nil
×
217
}
218

219
// SetFinalizer of Postgres cluster
UNCOV
220
func (client *KubernetesClient) SetFinalizer(clusterName spec.NamespacedName, pg *apiacidv1.Postgresql, finalizers []string) (*apiacidv1.Postgresql, error) {
×
UNCOV
221
        var (
×
UNCOV
222
                updatedPg *apiacidv1.Postgresql
×
UNCOV
223
                patch     []byte
×
UNCOV
224
                err       error
×
UNCOV
225
        )
×
UNCOV
226
        pg.ObjectMeta.Finalizers = finalizers
×
UNCOV
227

×
UNCOV
228
        if len(finalizers) > 0 {
×
UNCOV
229
                patch, err = json.Marshal(struct {
×
UNCOV
230
                        PgMetadata interface{} `json:"metadata"`
×
UNCOV
231
                }{&pg.ObjectMeta})
×
UNCOV
232
                if err != nil {
×
233
                        return pg, fmt.Errorf("could not marshal ObjectMeta: %v", err)
×
234
                }
×
235

UNCOV
236
                updatedPg, err = client.PostgresqlsGetter.Postgresqls(clusterName.Namespace).Patch(
×
UNCOV
237
                        context.TODO(), clusterName.Name, types.MergePatchType, patch, metav1.PatchOptions{})
×
238
        } else {
×
239
                // in case finalizers are empty and update is needed to remove
×
240
                updatedPg, err = client.PostgresqlsGetter.Postgresqls(clusterName.Namespace).Update(
×
241
                        context.TODO(), pg, metav1.UpdateOptions{})
×
242
        }
×
UNCOV
243
        if err != nil {
×
244
                return updatedPg, fmt.Errorf("could not set finalizer: %v", err)
×
245
        }
×
246

UNCOV
247
        return updatedPg, nil
×
248
}
249

UNCOV
250
func (c *mockCustomResourceDefinition) Get(ctx context.Context, name string, options metav1.GetOptions) (*apiextv1.CustomResourceDefinition, error) {
×
UNCOV
251
        return &apiextv1.CustomResourceDefinition{}, nil
×
UNCOV
252
}
×
253

254
func (c *mockCustomResourceDefinition) Create(ctx context.Context, crd *apiextv1.CustomResourceDefinition, options metav1.CreateOptions) (*apiextv1.CustomResourceDefinition, error) {
×
255
        return &apiextv1.CustomResourceDefinition{}, nil
×
256
}
×
257

UNCOV
258
func (mock *MockCustomResourceDefinitionsGetter) CustomResourceDefinitions() apiextv1client.CustomResourceDefinitionInterface {
×
UNCOV
259
        return &mockCustomResourceDefinition{}
×
UNCOV
260
}
×
261

UNCOV
262
func (c *mockSecret) Get(ctx context.Context, name string, options metav1.GetOptions) (*v1.Secret, error) {
×
UNCOV
263
        oldFormatSecret := &v1.Secret{}
×
UNCOV
264
        oldFormatSecret.Name = "testcluster"
×
UNCOV
265
        oldFormatSecret.Data = map[string][]byte{
×
UNCOV
266
                "user1":     []byte("testrole"),
×
UNCOV
267
                "password1": []byte("testpassword"),
×
UNCOV
268
                "inrole1":   []byte("testinrole"),
×
UNCOV
269
                "foobar":    []byte(b64.StdEncoding.EncodeToString([]byte("password"))),
×
UNCOV
270
        }
×
UNCOV
271

×
UNCOV
272
        newFormatSecret := &v1.Secret{}
×
UNCOV
273
        newFormatSecret.Name = "test-secret-new-format"
×
UNCOV
274
        newFormatSecret.Data = map[string][]byte{
×
UNCOV
275
                "user":       []byte("new-test-role"),
×
UNCOV
276
                "password":   []byte("new-test-password"),
×
UNCOV
277
                "inrole":     []byte("new-test-inrole"),
×
UNCOV
278
                "new-foobar": []byte(b64.StdEncoding.EncodeToString([]byte("password"))),
×
UNCOV
279
        }
×
UNCOV
280

×
UNCOV
281
        secrets := map[string]*v1.Secret{
×
UNCOV
282
                "infrastructureroles-old-test": oldFormatSecret,
×
UNCOV
283
                "infrastructureroles-new-test": newFormatSecret,
×
UNCOV
284
        }
×
UNCOV
285

×
UNCOV
286
        for idx := 1; idx <= 2; idx++ {
×
UNCOV
287
                newFormatStandaloneSecret := &v1.Secret{}
×
UNCOV
288
                newFormatStandaloneSecret.Name = fmt.Sprintf("test-secret-new-format%d", idx)
×
UNCOV
289
                newFormatStandaloneSecret.Data = map[string][]byte{
×
UNCOV
290
                        "user":     []byte(fmt.Sprintf("new-test-role%d", idx)),
×
UNCOV
291
                        "password": []byte(fmt.Sprintf("new-test-password%d", idx)),
×
UNCOV
292
                        "inrole":   []byte(fmt.Sprintf("new-test-inrole%d", idx)),
×
UNCOV
293
                }
×
UNCOV
294

×
UNCOV
295
                secrets[fmt.Sprintf("infrastructureroles-new-test%d", idx)] =
×
UNCOV
296
                        newFormatStandaloneSecret
×
UNCOV
297
        }
×
298

UNCOV
299
        if secret, exists := secrets[name]; exists {
×
UNCOV
300
                return secret, nil
×
UNCOV
301
        }
×
302

UNCOV
303
        return nil, fmt.Errorf("NotFound")
×
304

305
}
306

UNCOV
307
func (c *mockConfigMap) Get(ctx context.Context, name string, options metav1.GetOptions) (*v1.ConfigMap, error) {
×
UNCOV
308
        oldFormatConfigmap := &v1.ConfigMap{}
×
UNCOV
309
        oldFormatConfigmap.Name = "testcluster"
×
UNCOV
310
        oldFormatConfigmap.Data = map[string]string{
×
UNCOV
311
                "foobar": "{}",
×
UNCOV
312
        }
×
UNCOV
313

×
UNCOV
314
        newFormatConfigmap := &v1.ConfigMap{}
×
UNCOV
315
        newFormatConfigmap.Name = "testcluster"
×
UNCOV
316
        newFormatConfigmap.Data = map[string]string{
×
UNCOV
317
                "new-foobar": "{\"user_flags\": [\"createdb\"]}",
×
UNCOV
318
        }
×
UNCOV
319

×
UNCOV
320
        configmaps := map[string]*v1.ConfigMap{
×
UNCOV
321
                "infrastructureroles-old-test": oldFormatConfigmap,
×
UNCOV
322
                "infrastructureroles-new-test": newFormatConfigmap,
×
UNCOV
323
        }
×
UNCOV
324

×
UNCOV
325
        if configmap, exists := configmaps[name]; exists {
×
UNCOV
326
                return configmap, nil
×
UNCOV
327
        }
×
328

UNCOV
329
        return nil, fmt.Errorf("NotFound")
×
330
}
331

332
// Secrets to be mocked
UNCOV
333
func (mock *MockSecretGetter) Secrets(namespace string) corev1.SecretInterface {
×
UNCOV
334
        return &mockSecret{}
×
UNCOV
335
}
×
336

337
// ConfigMaps to be mocked
UNCOV
338
func (mock *MockConfigMapsGetter) ConfigMaps(namespace string) corev1.ConfigMapInterface {
×
UNCOV
339
        return &mockConfigMap{}
×
UNCOV
340
}
×
341

342
func (mock *MockDeploymentGetter) Deployments(namespace string) appsv1.DeploymentInterface {
×
343
        return &mockDeployment{}
×
344
}
×
345

346
func (mock *MockDeploymentNotExistGetter) Deployments(namespace string) appsv1.DeploymentInterface {
×
347
        return &mockDeploymentNotExist{}
×
348
}
×
349

350
func (mock *mockDeployment) Create(context.Context, *apiappsv1.Deployment, metav1.CreateOptions) (*apiappsv1.Deployment, error) {
×
351
        return &apiappsv1.Deployment{
×
352
                ObjectMeta: metav1.ObjectMeta{
×
353
                        Name: "test-deployment",
×
354
                },
×
355
                Spec: apiappsv1.DeploymentSpec{
×
356
                        Replicas: Int32ToPointer(1),
×
357
                },
×
358
        }, nil
×
359
}
×
360

361
func (mock *mockDeployment) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
×
362
        return nil
×
363
}
×
364

365
func (mock *mockDeployment) Get(ctx context.Context, name string, opts metav1.GetOptions) (*apiappsv1.Deployment, error) {
×
366
        return &apiappsv1.Deployment{
×
367
                ObjectMeta: metav1.ObjectMeta{
×
368
                        Name: "test-deployment",
×
369
                },
×
370
                Spec: apiappsv1.DeploymentSpec{
×
371
                        Replicas: Int32ToPointer(1),
×
372
                        Template: v1.PodTemplateSpec{
×
373
                                Spec: v1.PodSpec{
×
374
                                        Containers: []v1.Container{
×
375
                                                {
×
376
                                                        Image: "pooler:1.0",
×
377
                                                },
×
378
                                        },
×
379
                                },
×
380
                        },
×
381
                },
×
382
        }, nil
×
383
}
×
384

385
func (mock *mockDeployment) Patch(ctx context.Context, name string, t types.PatchType, data []byte, opts metav1.PatchOptions, subres ...string) (*apiappsv1.Deployment, error) {
×
386
        return &apiappsv1.Deployment{
×
387
                Spec: apiappsv1.DeploymentSpec{
×
388
                        Replicas: Int32ToPointer(2),
×
389
                },
×
390
                ObjectMeta: metav1.ObjectMeta{
×
391
                        Name: "test-deployment",
×
392
                },
×
393
        }, nil
×
394
}
×
395

396
func (mock *mockDeploymentNotExist) Get(ctx context.Context, name string, opts metav1.GetOptions) (*apiappsv1.Deployment, error) {
×
397
        return nil, &apierrors.StatusError{
×
398
                ErrStatus: metav1.Status{
×
399
                        Reason: metav1.StatusReasonNotFound,
×
400
                },
×
401
        }
×
402
}
×
403

404
func (mock *mockDeploymentNotExist) Create(context.Context, *apiappsv1.Deployment, metav1.CreateOptions) (*apiappsv1.Deployment, error) {
×
405
        return &apiappsv1.Deployment{
×
406
                ObjectMeta: metav1.ObjectMeta{
×
407
                        Name: "test-deployment",
×
408
                },
×
409
                Spec: apiappsv1.DeploymentSpec{
×
410
                        Replicas: Int32ToPointer(1),
×
411
                },
×
412
        }, nil
×
413
}
×
414

415
func (mock *MockServiceGetter) Services(namespace string) corev1.ServiceInterface {
×
416
        return &mockService{}
×
417
}
×
418

419
func (mock *MockServiceNotExistGetter) Services(namespace string) corev1.ServiceInterface {
×
420
        return &mockServiceNotExist{}
×
421
}
×
422

423
func (mock *mockService) Create(context.Context, *v1.Service, metav1.CreateOptions) (*v1.Service, error) {
×
424
        return &v1.Service{
×
425
                ObjectMeta: metav1.ObjectMeta{
×
426
                        Name: "test-service",
×
427
                },
×
428
        }, nil
×
429
}
×
430

431
func (mock *mockService) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
×
432
        return nil
×
433
}
×
434

435
func (mock *mockService) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Service, error) {
×
436
        return &v1.Service{
×
437
                ObjectMeta: metav1.ObjectMeta{
×
438
                        Name: "test-service",
×
439
                },
×
440
        }, nil
×
441
}
×
442

443
func (mock *mockServiceNotExist) Create(context.Context, *v1.Service, metav1.CreateOptions) (*v1.Service, error) {
×
444
        return &v1.Service{
×
445
                ObjectMeta: metav1.ObjectMeta{
×
446
                        Name: "test-service",
×
447
                },
×
448
        }, nil
×
449
}
×
450

451
func (mock *mockServiceNotExist) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Service, error) {
×
452
        return nil, &apierrors.StatusError{
×
453
                ErrStatus: metav1.Status{
×
454
                        Reason: metav1.StatusReasonNotFound,
×
455
                },
×
456
        }
×
457
}
×
458

459
// NewMockKubernetesClient for other tests
UNCOV
460
func NewMockKubernetesClient() KubernetesClient {
×
UNCOV
461
        return KubernetesClient{
×
UNCOV
462
                SecretsGetter:     &MockSecretGetter{},
×
UNCOV
463
                ConfigMapsGetter:  &MockConfigMapsGetter{},
×
UNCOV
464
                DeploymentsGetter: &MockDeploymentGetter{},
×
UNCOV
465
                ServicesGetter:    &MockServiceGetter{},
×
UNCOV
466

×
UNCOV
467
                CustomResourceDefinitionsGetter: &MockCustomResourceDefinitionsGetter{},
×
UNCOV
468
        }
×
UNCOV
469
}
×
470

471
func ClientMissingObjects() KubernetesClient {
×
472
        return KubernetesClient{
×
473
                DeploymentsGetter: &MockDeploymentNotExistGetter{},
×
474
                ServicesGetter:    &MockServiceNotExistGetter{},
×
475
        }
×
476
}
×
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

© 2025 Coveralls, Inc