Skip to content

Commit 45655a1

Browse files
JGAntunessgalsalehajp-io
authored
feat(ec-updates): don't allow updates of >1 minor kube versions (#5471)
* feat(ec-updates): don't allow updates of >1 minor kube versions * chore: re-work the available updates method * chore: move regex compile to global * chore: review copy of the errors used * Update pkg/update/required.go Co-authored-by: Alex Parker <[email protected]> * Update pkg/update/required.go Co-authored-by: Alex Parker <[email protected]> * fix tests --------- Co-authored-by: Salah Al Saleh <[email protected]> Co-authored-by: Alex Parker <[email protected]>
1 parent 6e3dea6 commit 45655a1

File tree

9 files changed

+1065
-244
lines changed

9 files changed

+1065
-244
lines changed

pkg/update/required.go

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,50 @@ import (
1111
"github.com/replicatedhq/kots/pkg/cursor"
1212
"github.com/replicatedhq/kots/pkg/kotsutil"
1313
"github.com/replicatedhq/kots/pkg/store"
14+
"github.com/replicatedhq/kots/pkg/update/types"
1415
upstreamtypes "github.com/replicatedhq/kots/pkg/upstream/types"
16+
"github.com/replicatedhq/kots/pkg/util"
1517
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
1618
)
1719

18-
func isUpdateDeployable(updateCursor string, updates []upstreamtypes.Update) (bool, string) {
19-
// iterate over updates in reverse since they are sorted in descending order
20+
// getAvailableUpdates returns the slice of available updates given a list of upstream updates making sure to:
21+
// - Check for previously required versions and setting the deployabled and cause properties accordingly
22+
// - Check for kubernetes version compatibility for embedded cluster versions and setting the deployable and cause properties accordingly
23+
func getAvailableUpdates(updates []upstreamtypes.Update, currentECVersion string) []types.AvailableUpdate {
24+
availableUpdates := make([]types.AvailableUpdate, len(updates))
25+
26+
// keep the required updates in a slice in order to add these to the cause properties of each update
2027
requiredUpdates := []string{}
28+
// iterate over all updates in reverse since they are sorted in descending order
2129
for i := len(updates) - 1; i >= 0; i-- {
22-
if updates[i].Cursor == updateCursor {
23-
break
30+
upstreamUpdate := updates[i]
31+
availableUpdates[i] = types.AvailableUpdate{
32+
VersionLabel: upstreamUpdate.VersionLabel,
33+
UpdateCursor: upstreamUpdate.Cursor,
34+
ChannelID: upstreamUpdate.ChannelID,
35+
IsRequired: upstreamUpdate.IsRequired,
36+
UpstreamReleasedAt: upstreamUpdate.ReleasedAt,
37+
ReleaseNotes: upstreamUpdate.ReleaseNotes,
38+
IsDeployable: true,
2439
}
25-
if updates[i].IsRequired {
26-
requiredUpdates = append(requiredUpdates, updates[i].VersionLabel)
40+
// if there's any required before the current update, mark it as non-deployable and set the cause
41+
if len(requiredUpdates) > 0 {
42+
availableUpdates[i].IsDeployable = false
43+
availableUpdates[i].NonDeployableCause = getRequiredNonDeployableCause(requiredUpdates)
44+
// else check the k8s versions are compatible but only do so if the update has an embeded cluster version specificied
45+
} else if upstreamUpdate.EmbeddedClusterVersion != "" {
46+
if err := util.UpdateWithinKubeRange(currentECVersion, upstreamUpdate.EmbeddedClusterVersion); err != nil {
47+
availableUpdates[i].IsDeployable = false
48+
availableUpdates[i].NonDeployableCause = getKubeVersionNonDeployableCause(err)
49+
}
50+
}
51+
// if this update is required add it to the slice so that we can mention it for the next updates
52+
if upstreamUpdate.IsRequired {
53+
requiredUpdates = append(requiredUpdates, upstreamUpdate.VersionLabel)
2754
}
2855
}
29-
if len(requiredUpdates) > 0 {
30-
return false, getNonDeployableCause(requiredUpdates)
31-
}
32-
return true, ""
56+
57+
return availableUpdates
3358
}
3459

3560
func IsAirgapUpdateDeployable(app *apptypes.App, airgap *kotsv1beta1.Airgap) (bool, string, error) {
@@ -46,7 +71,7 @@ func IsAirgapUpdateDeployable(app *apptypes.App, airgap *kotsv1beta1.Airgap) (bo
4671
return false, "", errors.Wrap(err, "failed to get missing required versions")
4772
}
4873
if len(requiredUpdates) > 0 {
49-
return false, getNonDeployableCause(requiredUpdates), nil
74+
return false, getRequiredNonDeployableCause(requiredUpdates), nil
5075
}
5176
return true, "", nil
5277
}
@@ -105,7 +130,8 @@ func getRequiredAirgapUpdates(airgap *kotsv1beta1.Airgap, license *kotsv1beta1.L
105130
return requiredUpdates, nil
106131
}
107132

108-
func getNonDeployableCause(requiredUpdates []string) string {
133+
// getRequiredNonDeployableCause constructs a non-deployable cause message based on the required updates.
134+
func getRequiredNonDeployableCause(requiredUpdates []string) string {
109135
if len(requiredUpdates) == 0 {
110136
return ""
111137
}
@@ -119,3 +145,16 @@ func getNonDeployableCause(requiredUpdates []string) string {
119145
}
120146
return fmt.Sprintf("This version cannot be deployed because versions %s are required and must be deployed first.", versionLabelsStr)
121147
}
148+
149+
// getKubeVersionNonDeployableCause constructs a non-deployable cause message based on the kube range validation error message
150+
func getKubeVersionNonDeployableCause(err error) string {
151+
switch {
152+
case errors.Is(err, util.ErrKubeMinorRangeMismatch):
153+
return "Before you can update to this version, you need to update to an earlier version that includes the required infrastructure update."
154+
case errors.Is(err, util.ErrKubeVersionDowngrade):
155+
return "This version cannot be deployed because it would downgrade the infrastructure, which is unsupported."
156+
case errors.Is(err, util.ErrKubeMajorVersionUpgrade):
157+
return "Release includes a major version upgrade of the infrastructure version, which is not allowed. Cannot use release."
158+
}
159+
return "This version cannot be deployed because the required infrastructure compatibility could not be verified."
160+
}

pkg/update/update.go

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/replicatedhq/kots/pkg/update/types"
1515
upstreampkg "github.com/replicatedhq/kots/pkg/upstream"
1616
upstreamtypes "github.com/replicatedhq/kots/pkg/upstream/types"
17+
"github.com/replicatedhq/kots/pkg/util"
1718
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
1819
"go.uber.org/zap"
1920
)
@@ -57,20 +58,9 @@ func GetAvailableUpdates(kotsStore storepkg.Store, app *apptypes.App, license *k
5758
return nil, errors.Wrap(err, "failed to get updates")
5859
}
5960

60-
availableUpdates := []types.AvailableUpdate{}
61-
for _, u := range updates.Updates {
62-
deployable, cause := isUpdateDeployable(u.Cursor, updates.Updates)
63-
availableUpdates = append(availableUpdates, types.AvailableUpdate{
64-
VersionLabel: u.VersionLabel,
65-
UpdateCursor: u.Cursor,
66-
ChannelID: u.ChannelID,
67-
IsRequired: u.IsRequired,
68-
UpstreamReleasedAt: u.ReleasedAt,
69-
ReleaseNotes: u.ReleaseNotes,
70-
IsDeployable: deployable,
71-
NonDeployableCause: cause,
72-
})
73-
}
61+
currentECVersion := util.EmbeddedClusterVersion()
62+
63+
availableUpdates := getAvailableUpdates(updates.Updates, currentECVersion)
7464

7565
return availableUpdates, nil
7666
}

0 commit comments

Comments
 (0)