Skip to content

Commit f763334

Browse files
committed
fix: handle Azure storage in backupStorageName function
the backupStorageName function was causing nil pointer dereference crashes when processing Azure backups because it assumed all repos were S3 storage. updated to check storage type and match appropriate fields for both S3 and Azure storage types. added unit tests to verify correct behavior for S3, Azure, namespace mismatch, and error cases.
1 parent 7e2bef8 commit f763334

2 files changed

Lines changed: 249 additions & 4 deletions

File tree

internal/controller/everest/databaseclusterbackup_controller.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,10 +1068,29 @@ func backupStorageName(repoName string, pg *pgv2.PerconaPGCluster, storages *eve
10681068
for _, repo := range pg.Spec.Backups.PGBackRest.Repos {
10691069
if repo.Name == repoName {
10701070
for _, storage := range storages.Items {
1071-
if pg.Namespace == storage.Namespace &&
1072-
repo.S3.Region == storage.Spec.Region &&
1073-
repo.S3.Bucket == storage.Spec.Bucket &&
1074-
repo.S3.Endpoint == storage.Spec.EndpointURL {
1071+
// Namespace must always match
1072+
if pg.Namespace != storage.Namespace {
1073+
continue
1074+
}
1075+
1076+
// Match based on storage type
1077+
var matches bool
1078+
switch storage.Spec.Type {
1079+
case everestv1alpha1.BackupStorageTypeS3:
1080+
// For S3, match region, bucket, and endpoint
1081+
if repo.S3 != nil {
1082+
matches = repo.S3.Region == storage.Spec.Region &&
1083+
repo.S3.Bucket == storage.Spec.Bucket &&
1084+
repo.S3.Endpoint == storage.Spec.EndpointURL
1085+
}
1086+
case everestv1alpha1.BackupStorageTypeAzure:
1087+
// For Azure, match container (stored in BackupStorage.Spec.Bucket field)
1088+
if repo.Azure != nil {
1089+
matches = repo.Azure.Container == storage.Spec.Bucket
1090+
}
1091+
}
1092+
1093+
if matches {
10751094
return storage.Name, nil
10761095
}
10771096
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// everest-operator
2+
// Copyright (C) 2022 Percona LLC
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package everest
17+
18+
import (
19+
"testing"
20+
21+
crunchyv1beta1 "github.com/percona/percona-postgresql-operator/v2/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
22+
pgv2 "github.com/percona/percona-postgresql-operator/v2/pkg/apis/pgv2.percona.com/v2"
23+
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
27+
everestv1alpha1 "github.com/percona/everest-operator/api/everest/v1alpha1"
28+
)
29+
30+
func TestBackupStorageName(t *testing.T) {
31+
t.Parallel()
32+
33+
tests := []struct {
34+
name string
35+
repoName string
36+
pgCluster *pgv2.PerconaPGCluster
37+
storages *everestv1alpha1.BackupStorageList
38+
expectedName string
39+
expectError bool
40+
}{
41+
{
42+
name: "S3 storage match",
43+
repoName: "repo1",
44+
pgCluster: &pgv2.PerconaPGCluster{
45+
ObjectMeta: metav1.ObjectMeta{
46+
Name: "test-cluster",
47+
Namespace: "default",
48+
},
49+
Spec: pgv2.PerconaPGClusterSpec{
50+
Backups: pgv2.Backups{
51+
PGBackRest: pgv2.PGBackRestArchive{
52+
Repos: []crunchyv1beta1.PGBackRestRepo{
53+
{
54+
Name: "repo1",
55+
S3: &crunchyv1beta1.RepoS3{
56+
Bucket: "my-s3-bucket",
57+
Endpoint: "s3.amazonaws.com",
58+
Region: "us-east-1",
59+
},
60+
},
61+
},
62+
},
63+
},
64+
},
65+
},
66+
storages: &everestv1alpha1.BackupStorageList{
67+
Items: []everestv1alpha1.BackupStorage{
68+
{
69+
ObjectMeta: metav1.ObjectMeta{
70+
Name: "s3-storage",
71+
Namespace: "default",
72+
},
73+
Spec: everestv1alpha1.BackupStorageSpec{
74+
Type: everestv1alpha1.BackupStorageTypeS3,
75+
Bucket: "my-s3-bucket",
76+
Region: "us-east-1",
77+
EndpointURL: "s3.amazonaws.com",
78+
},
79+
},
80+
},
81+
},
82+
expectedName: "s3-storage",
83+
expectError: false,
84+
},
85+
{
86+
name: "Azure storage match",
87+
repoName: "repo2",
88+
pgCluster: &pgv2.PerconaPGCluster{
89+
ObjectMeta: metav1.ObjectMeta{
90+
Name: "test-cluster",
91+
Namespace: "default",
92+
},
93+
Spec: pgv2.PerconaPGClusterSpec{
94+
Backups: pgv2.Backups{
95+
PGBackRest: pgv2.PGBackRestArchive{
96+
Repos: []crunchyv1beta1.PGBackRestRepo{
97+
{
98+
Name: "repo2",
99+
Azure: &crunchyv1beta1.RepoAzure{
100+
Container: "database-backups",
101+
},
102+
},
103+
},
104+
},
105+
},
106+
},
107+
},
108+
storages: &everestv1alpha1.BackupStorageList{
109+
Items: []everestv1alpha1.BackupStorage{
110+
{
111+
ObjectMeta: metav1.ObjectMeta{
112+
Name: "azure-storage",
113+
Namespace: "default",
114+
},
115+
Spec: everestv1alpha1.BackupStorageSpec{
116+
Type: everestv1alpha1.BackupStorageTypeAzure,
117+
Bucket: "database-backups",
118+
},
119+
},
120+
},
121+
},
122+
expectedName: "azure-storage",
123+
expectError: false,
124+
},
125+
{
126+
name: "Namespace mismatch",
127+
repoName: "repo1",
128+
pgCluster: &pgv2.PerconaPGCluster{
129+
ObjectMeta: metav1.ObjectMeta{
130+
Name: "test-cluster",
131+
Namespace: "default",
132+
},
133+
Spec: pgv2.PerconaPGClusterSpec{
134+
Backups: pgv2.Backups{
135+
PGBackRest: pgv2.PGBackRestArchive{
136+
Repos: []crunchyv1beta1.PGBackRestRepo{
137+
{
138+
Name: "repo1",
139+
S3: &crunchyv1beta1.RepoS3{
140+
Bucket: "my-bucket",
141+
Endpoint: "s3.amazonaws.com",
142+
Region: "us-east-1",
143+
},
144+
},
145+
},
146+
},
147+
},
148+
},
149+
},
150+
storages: &everestv1alpha1.BackupStorageList{
151+
Items: []everestv1alpha1.BackupStorage{
152+
{
153+
ObjectMeta: metav1.ObjectMeta{
154+
Name: "s3-storage",
155+
Namespace: "other-namespace",
156+
},
157+
Spec: everestv1alpha1.BackupStorageSpec{
158+
Type: everestv1alpha1.BackupStorageTypeS3,
159+
Bucket: "my-bucket",
160+
Region: "us-east-1",
161+
EndpointURL: "s3.amazonaws.com",
162+
},
163+
},
164+
},
165+
},
166+
expectedName: "",
167+
expectError: true,
168+
},
169+
{
170+
name: "Repo not found",
171+
repoName: "nonexistent-repo",
172+
pgCluster: &pgv2.PerconaPGCluster{
173+
ObjectMeta: metav1.ObjectMeta{
174+
Name: "test-cluster",
175+
Namespace: "default",
176+
},
177+
Spec: pgv2.PerconaPGClusterSpec{
178+
Backups: pgv2.Backups{
179+
PGBackRest: pgv2.PGBackRestArchive{
180+
Repos: []crunchyv1beta1.PGBackRestRepo{
181+
{
182+
Name: "repo1",
183+
S3: &crunchyv1beta1.RepoS3{
184+
Bucket: "my-bucket",
185+
},
186+
},
187+
},
188+
},
189+
},
190+
},
191+
},
192+
storages: &everestv1alpha1.BackupStorageList{
193+
Items: []everestv1alpha1.BackupStorage{
194+
{
195+
ObjectMeta: metav1.ObjectMeta{
196+
Name: "s3-storage",
197+
Namespace: "default",
198+
},
199+
Spec: everestv1alpha1.BackupStorageSpec{
200+
Type: everestv1alpha1.BackupStorageTypeS3,
201+
Bucket: "my-bucket",
202+
},
203+
},
204+
},
205+
},
206+
expectedName: "",
207+
expectError: true,
208+
},
209+
}
210+
211+
for _, tt := range tests {
212+
t.Run(tt.name, func(t *testing.T) {
213+
t.Parallel()
214+
215+
name, err := backupStorageName(tt.repoName, tt.pgCluster, tt.storages)
216+
217+
if tt.expectError {
218+
require.Error(t, err)
219+
assert.Empty(t, name)
220+
} else {
221+
require.NoError(t, err)
222+
assert.Equal(t, tt.expectedName, name)
223+
}
224+
})
225+
}
226+
}

0 commit comments

Comments
 (0)