Skip to content

Commit 1704bd4

Browse files
authored
Adjust upgrade valid check (CrunchyData#3223)
The upgradeTagValid was originally checking tags as strings which led to 12.9=>12.10 upgrades being rejected as invalid. This changes that validator to check certain parts of the tag as ints rather than strings, and adds a unit test. Issue [sc-14575]
1 parent 698fff0 commit 1704bd4

File tree

2 files changed

+95
-12
lines changed

2 files changed

+95
-12
lines changed

internal/apiserver/upgradeservice/upgradeimpl.go

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -230,44 +230,61 @@ func supportedOperatorVersion(version string) bool {
230230
// upgradeTagValid compares and validates the PostgreSQL version values stored
231231
// in the image tag of the existing pgcluster CR against the values set in the
232232
// Postgres Operator's configuration
233+
// A typical example tag is `ubi8-12.9-4.7.4`, so we want to extract and
234+
// compare that `12.9` to make sure that we are only allowing minor upgrades.
235+
// For major upgrades, see PGOv5.1.
233236
func upgradeTagValid(upgradeFrom, upgradeTo string) bool {
234237
log.Debugf("Validating upgrade from %s to %s", upgradeFrom, upgradeTo)
235238

236-
versionRegex := regexp.MustCompile(`-(\d+)\.(\d+)(\.\d+)?-`)
239+
versionRegex := regexp.MustCompile(`-(\d+)\.(\d+)\.?(\d+)?-`)
237240

238241
// get the PostgreSQL version values
239242
upgradeFromValue := versionRegex.FindStringSubmatch(upgradeFrom)
240243
upgradeToValue := versionRegex.FindStringSubmatch(upgradeTo)
241244

242245
// if this regex passes, the returned array should always contain
243-
// 4 values. At 0, the full match, then 1-3 are the three defined groups
244-
// If this is not true, the upgrade cannot continue (and we won't want to
245-
// reference potentially missing array items).
246+
// 4 values:
247+
// -At 0, the full match;
248+
// -At 1, the major version of PG;
249+
// -At 2, the minor version of PG, which needs to be compared as ints;
250+
// -At 3, the patch version, which can be null, but if not, should be compared as ints;
251+
// (Note the `?` in the regex after the last capture group.)
246252
if len(upgradeFromValue) != 4 || len(upgradeToValue) != 4 {
247253
return false
248254
}
249255

250-
// if the first group does not match (PG version 9, 10, 11, 12 etc), or if a value is
256+
// if the first group does not match (i.e., the PG major version), or if a value is
251257
// missing, then the upgrade cannot continue
252258
if upgradeFromValue[1] != upgradeToValue[1] && upgradeToValue[1] != "" {
253259
return false
254260
}
255261

256-
// if the above check passed, and there is no fourth value, then the PG
257-
// version has only two digits (e.g. PG 10, 11 or 12), meaning this is a minor upgrade.
262+
// if the above check passed, and there is no patch version value, then the PG
263+
// version has only two digits (e.g. PG 12.6, 12.10), meaning this is a minor upgrade.
258264
// After validating the second value is at least equal (this is to allow for multiple executions of the
259265
// upgrade in case an error occurs), the upgrade can continue
260-
if upgradeFromValue[3] == "" && upgradeToValue[3] == "" && upgradeFromValue[2] <= upgradeToValue[2] {
266+
// In order to compare correctly, these values have to be ints.
267+
// Note: thanks to the regex capture, we know these second values consist of digits,
268+
// so we can skip testing the error.
269+
270+
upgradeFromInt, _ := strconv.Atoi(upgradeFromValue[2])
271+
upgradeToInt, _ := strconv.Atoi(upgradeToValue[2])
272+
if upgradeFromValue[3] == "" && upgradeToValue[3] == "" && upgradeFromInt <= upgradeToInt {
261273
return true
262274
}
263275

264276
// finally, if the second group matches and is not empty, then, based on the
265277
// possibilities remaining for Operator container image tags, this is either PG 9.5 or 9.6.
266-
// if the second group value matches, and the third group was already validated as not
267-
// empty, check that the third value is at least equal (this is to allow for multiple executions of the
278+
// if the second group value matches, check that the third value is not empty and
279+
// at least equal (this is to allow for multiple executions of the
268280
// upgrade in case an error occurs). If so, the upgrade can continue.
269-
if upgradeFromValue[2] == upgradeToValue[2] && upgradeToValue[2] != "" && upgradeFromValue[3] <= upgradeToValue[3] {
270-
return true
281+
if upgradeFromValue[2] == upgradeToValue[2] && upgradeToValue[2] != "" &&
282+
upgradeFromValue[3] != "" && upgradeToValue[3] != "" {
283+
upgradeFromInt, _ = strconv.Atoi(upgradeFromValue[3])
284+
upgradeToInt, _ = strconv.Atoi(upgradeToValue[3])
285+
if upgradeFromInt <= upgradeToInt {
286+
return true
287+
}
271288
}
272289

273290
// if none of the above conditions are met, a two digit Major version upgrade is likely being
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package upgradeservice
2+
3+
/*
4+
Copyright 2021 - 2022 Crunchy Data Solutions, Inc.
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
import (
19+
"fmt"
20+
"testing"
21+
)
22+
23+
func TestUpgradeTagValid(t *testing.T) {
24+
tests := []struct {
25+
fromTag string
26+
toTag string
27+
expected bool
28+
}{
29+
// Bad tags
30+
// Too short
31+
{fromTag: "ubi8-12-4.7.4", toTag: "ubi8-12.10-4.7.5", expected: false},
32+
{fromTag: "ubi8-12.9-4.7.4", toTag: "ubi8-12-4.7.5", expected: false},
33+
// Too long
34+
{fromTag: "ubi8-12.9.10.3-4.7.4", toTag: "ubi8-12.10-4.7.5", expected: false},
35+
{fromTag: "ubi8-12.9-4.7.4", toTag: "ubi8-12.10.3.4-4.7.5", expected: false},
36+
// Not digits
37+
{fromTag: "ubi8-12.9.hello-4.7.4", toTag: "ubi8-12.10-4.7.5", expected: false},
38+
{fromTag: "ubi8-12.9-4.7.4", toTag: "ubi8-12.10.hello-4.7.5", expected: false},
39+
{fromTag: "ubi8-12.hello-4.7.4", toTag: "ubi8-12-4.7.5", expected: false},
40+
{fromTag: "ubi8-12.9-4.7.4", toTag: "ubi8-12.hello-4.7.5", expected: false},
41+
// Mismatched major version
42+
{fromTag: "ubi8-12.9-4.7.4", toTag: "ubi8-13.10-4.7.5", expected: false},
43+
{fromTag: "ubi8-14.9-4.7.4", toTag: "ubi8-13.10-4.7.5", expected: false},
44+
// Patch should be absent if comparing minor values
45+
{fromTag: "ubi8-12.9.3-4.7.4", toTag: "ubi8-12.10-4.7.5", expected: false},
46+
{fromTag: "ubi8-12.9-4.7.4", toTag: "ubi8-12.10.3-4.7.5", expected: false},
47+
// From values higher than to values for minor or patch values
48+
// Note values chosen here are 9=>10 to test that int conversion occurs
49+
{fromTag: "ubi8-12.10-4.7.4", toTag: "ubi8-12.9-4.7.5", expected: false},
50+
{fromTag: "ubi8-12.7.10-4.7.4", toTag: "ubi8-12.7.9-4.7.5", expected: false},
51+
// Patch value partially absent
52+
{fromTag: "ubi8-12.9.3-4.7.4", toTag: "ubi8-12.9-4.7.5", expected: false},
53+
{fromTag: "ubi8-12.10-4.7.4", toTag: "ubi8-12.10.3-4.7.5", expected: false},
54+
// Valid
55+
{fromTag: "ubi8-12.9-4.7.4", toTag: "ubi8-12.10-4.7.5", expected: true},
56+
{fromTag: "ubi8-12.9-4.7.4", toTag: "ubi8-12.9-4.7.5", expected: true},
57+
{fromTag: "ubi8-12.9.9-4.7.4", toTag: "ubi8-12.9.10-4.7.5", expected: true},
58+
}
59+
for _, test := range tests {
60+
t.Run(fmt.Sprintf("%s=>%s", test.fromTag, test.toTag), func(t *testing.T) {
61+
if upgradeTagValid(test.fromTag, test.toTag) != test.expected {
62+
t.Fatalf("expected %t", test.expected)
63+
}
64+
})
65+
}
66+
}

0 commit comments

Comments
 (0)