Skip to content

Commit cbc1221

Browse files
JGAntunesemosbaughajp-io
authored
feat(ec): do not allow upgrading more than 1 k8s minor (#5313)
* feat(ec): do not allow upgrading more than 1 k8s minor * chore: tests * chore: test versions with a v prefix * Update pkg/upgradeservice/bootstrap.go Co-authored-by: Ethan Mosbaugh <[email protected]> * Update pkg/upgradeservice/bootstrap.go Co-authored-by: Ethan Mosbaugh <[email protected]> * fix: also validate downgrades * fix: also validate downgrades, but stop them entirely * chore: cleanup commit garbadge * Update pkg/upgradeservice/bootstrap.go Co-authored-by: Alex Parker <[email protected]> * Update pkg/upgradeservice/bootstrap.go Co-authored-by: Alex Parker <[email protected]> * Update pkg/upgradeservice/bootstrap.go Co-authored-by: Alex Parker <[email protected]> * fix: tests --------- Co-authored-by: Ethan Mosbaugh <[email protected]> Co-authored-by: Alex Parker <[email protected]>
1 parent 82508e1 commit cbc1221

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

pkg/upgradeservice/bootstrap.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"io"
77
"os"
88
"path/filepath"
9+
"regexp"
910

11+
"github.com/Masterminds/semver"
1012
"github.com/pkg/errors"
1113
"github.com/replicatedhq/kots/pkg/archives"
1214
"github.com/replicatedhq/kots/pkg/k8sutil"
@@ -22,6 +24,10 @@ import (
2224
)
2325

2426
func bootstrap(params types.UpgradeServiceParams) (finalError error) {
27+
if err := updateWithinKubeRange(params); err != nil {
28+
return errors.Wrap(err, "kubernetes version update is not within allowed range")
29+
}
30+
2531
if err := k8sutil.InitHelmCapabilities(); err != nil {
2632
return errors.Wrap(err, "failed to init helm capabilities")
2733
}
@@ -40,6 +46,44 @@ func bootstrap(params types.UpgradeServiceParams) (finalError error) {
4046
return nil
4147
}
4248

49+
// updateWithinKubeRange checks if the update version is within the same major version and
50+
// at most one minor version ahead of the current version.
51+
func updateWithinKubeRange(params types.UpgradeServiceParams) error {
52+
currentVersion, err := extractKubeVersion(params.CurrentECVersion)
53+
if err != nil {
54+
return errors.Wrap(err, "failed to extract current kube version")
55+
}
56+
updateVersion, err := extractKubeVersion(params.UpdateECVersion)
57+
if err != nil {
58+
return errors.Wrap(err, "failed to extract update kube version")
59+
}
60+
if currentVersion.Major() != updateVersion.Major() {
61+
return errors.Errorf("major version mismatch: current %s, update %s", currentVersion, updateVersion)
62+
}
63+
if currentVersion.GreaterThan(updateVersion) {
64+
return errors.Errorf("cannot downgrade the kubernetes version: current %s, update %s", currentVersion, updateVersion)
65+
}
66+
if updateVersion.Minor() > currentVersion.Minor()+1 {
67+
return errors.Errorf("cannot update by more than one kubernetes minor version: current %s, update %s", currentVersion, updateVersion)
68+
}
69+
return nil
70+
}
71+
72+
// Utility method to extract the kube version from an EC version.
73+
// Given a version string like "2.4.0+k8s-1.30-rc0", it returns the kube semver version "1.30"
74+
func extractKubeVersion(ecVersion string) (*semver.Version, error) {
75+
re := regexp.MustCompile(`\+k8s-(\d+\.\d+)`)
76+
matches := re.FindStringSubmatch(ecVersion)
77+
if len(matches) != 2 {
78+
return nil, errors.Errorf("failed to extract kube version from '%s'", ecVersion)
79+
}
80+
kubeVersion, err := semver.NewVersion(matches[1])
81+
if err != nil {
82+
return nil, errors.Wrapf(err, "failed to parse kube version from '%s'", ecVersion)
83+
}
84+
return kubeVersion, nil
85+
}
86+
4387
func pullArchiveFromAirgap(params types.UpgradeServiceParams) (finalError error) {
4488
airgapRoot, err := archives.ExtractAppMetaFromAirgapBundle(params.UpdateAirgapBundle)
4589
if err != nil {

pkg/upgradeservice/bootstrap_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package upgradeservice
2+
3+
import (
4+
"testing"
5+
6+
"github.com/replicatedhq/kots/pkg/upgradeservice/types"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestUpdateWithinKubeRange(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
params types.UpgradeServiceParams
15+
expectError bool
16+
expectedErrMsg string
17+
}{
18+
{
19+
name: "same version",
20+
params: types.UpgradeServiceParams{
21+
CurrentECVersion: "2.4.0+k8s-1.30-rc0",
22+
UpdateECVersion: "2.4.1+k8s-1.30-rc1",
23+
},
24+
expectError: false,
25+
},
26+
{
27+
name: "one minor version upgrade",
28+
params: types.UpgradeServiceParams{
29+
CurrentECVersion: "2.4.0+k8s-1.30-rc0",
30+
UpdateECVersion: "2.5.0+k8s-1.31-rc0",
31+
},
32+
expectError: false,
33+
},
34+
{
35+
name: "two minor version upgrade",
36+
params: types.UpgradeServiceParams{
37+
CurrentECVersion: "2.4.0+k8s-1.30-rc0",
38+
UpdateECVersion: "2.6.0+k8s-1.32-rc0",
39+
},
40+
expectError: true,
41+
expectedErrMsg: "cannot update by more than one kubernetes minor version",
42+
},
43+
{
44+
name: "one minor version downgrade",
45+
params: types.UpgradeServiceParams{
46+
CurrentECVersion: "2.4.0+k8s-1.31-rc0",
47+
UpdateECVersion: "2.5.0+k8s-1.30-rc0",
48+
},
49+
expectError: true,
50+
expectedErrMsg: "cannot downgrade the kubernetes version",
51+
},
52+
{
53+
name: "major version mismatch",
54+
params: types.UpgradeServiceParams{
55+
CurrentECVersion: "2.4.0+k8s-1.30-rc0",
56+
UpdateECVersion: "2.5.0+k8s-2.31-rc0",
57+
},
58+
expectError: true,
59+
expectedErrMsg: "major version mismatch",
60+
},
61+
{
62+
name: "invalid current version format",
63+
params: types.UpgradeServiceParams{
64+
CurrentECVersion: "2.4.0-invalid-format",
65+
UpdateECVersion: "2.5.0+k8s-1.31-rc0",
66+
},
67+
expectError: true,
68+
expectedErrMsg: "failed to extract current kube version",
69+
},
70+
{
71+
name: "invalid update version format",
72+
params: types.UpgradeServiceParams{
73+
CurrentECVersion: "2.4.0+k8s-1.30-rc0",
74+
UpdateECVersion: "2.5.0-invalid-format",
75+
},
76+
expectError: true,
77+
expectedErrMsg: "failed to extract update kube version",
78+
},
79+
}
80+
81+
for _, tt := range tests {
82+
t.Run(tt.name, func(t *testing.T) {
83+
err := updateWithinKubeRange(tt.params)
84+
if tt.expectError {
85+
require.Error(t, err)
86+
assert.Contains(t, err.Error(), tt.expectedErrMsg)
87+
} else {
88+
require.NoError(t, err)
89+
}
90+
})
91+
}
92+
}
93+
94+
func TestExtractKubeVersion(t *testing.T) {
95+
tests := []struct {
96+
name string
97+
ecVersion string
98+
expectedVersion string
99+
expectError bool
100+
}{
101+
{
102+
name: "valid format",
103+
ecVersion: "2.4.0+k8s-1.30-rc0",
104+
expectedVersion: "1.30.0",
105+
expectError: false,
106+
},
107+
{
108+
name: "another valid format",
109+
ecVersion: "3.1.5+k8s-1.29",
110+
expectedVersion: "1.29.0",
111+
expectError: false,
112+
},
113+
{
114+
name: "ec version with a v prefix is valid",
115+
ecVersion: "v3.1.5+k8s-1.29",
116+
expectedVersion: "1.29.0",
117+
expectError: false,
118+
},
119+
{
120+
name: "invalid format - missing k8s tag",
121+
ecVersion: "2.4.0-rc0",
122+
expectError: true,
123+
},
124+
{
125+
name: "invalid format - malformed version",
126+
ecVersion: "2.4.0+k8s-abc-rc0",
127+
expectError: true,
128+
},
129+
}
130+
131+
for _, tt := range tests {
132+
t.Run(tt.name, func(t *testing.T) {
133+
version, err := extractKubeVersion(tt.ecVersion)
134+
if tt.expectError {
135+
require.Error(t, err)
136+
} else {
137+
require.NoError(t, err)
138+
assert.Equal(t, tt.expectedVersion, version.String())
139+
}
140+
})
141+
}
142+
}

0 commit comments

Comments
 (0)