Skip to content

Commit df62f74

Browse files
committed
chore: address feedback and fix airgap required version setup
1 parent 2fabba8 commit df62f74

File tree

6 files changed

+444
-472
lines changed

6 files changed

+444
-472
lines changed

cmd/installer/cli/upgrade.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ func preRunUpgrade(ctx context.Context, flags UpgradeCmdFlags, upgradeConfig *up
258258
}
259259
upgradeConfig.license = l
260260

261-
// sync the license if a license is provided and we are not in airgap mode
262-
if upgradeConfig.license != nil && flags.airgapBundle == "" {
261+
// sync the license and initialize the replicated api client if we are not in airgap mode
262+
if flags.airgapBundle == "" {
263263
replicatedAPI, err := newReplicatedAPIClient(upgradeConfig.license, upgradeConfig.clusterID)
264264
if err != nil {
265265
return fmt.Errorf("failed to create replicated API client: %w", err)
@@ -607,11 +607,9 @@ func validateIsReleaseUpgradable(ctx context.Context, upgradeConfig upgradeConfi
607607

608608
// Build validation options
609609
opts := validation.UpgradableOptions{
610-
IsAirgap: isAirgap,
611610
CurrentECVersion: currentECVersion,
612611
TargetECVersion: targetECVersion,
613612
License: upgradeConfig.license,
614-
AirgapMetadata: upgradeConfig.airgapMetadata,
615613
}
616614

617615
// Add current app version info if available
@@ -624,10 +622,15 @@ func validateIsReleaseUpgradable(ctx context.Context, upgradeConfig upgradeConfi
624622
opts.TargetAppVersion = channelRelease.VersionLabel
625623
opts.TargetAppSequence = channelRelease.ChannelSequence
626624

627-
// For online upgrades, add the replicated API client and channel ID
628-
if upgradeConfig.replicatedAPIClient != nil {
629-
opts.ReplicatedAPI = upgradeConfig.replicatedAPIClient
630-
opts.ChannelID = channelRelease.ChannelID
625+
// Extract the required releases depending on if it's airgap or online
626+
if isAirgap {
627+
if err := opts.WithAirgapRequiredReleases(upgradeConfig.airgapMetadata); err != nil {
628+
return fmt.Errorf("failed to extract required releases from airgap metadata: %w", err)
629+
}
630+
} else {
631+
if err := opts.WithOnlineRequiredReleases(ctx, upgradeConfig.replicatedAPIClient); err != nil {
632+
return fmt.Errorf("failed to extract required releases from replidated API's pending release call: %w", err)
633+
}
631634
}
632635

633636
// Perform validation

pkg-new/replicatedapi/mock.go

Lines changed: 0 additions & 32 deletions
This file was deleted.

pkg-new/validation/errors.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,6 @@ func (e *ValidationError) Error() string {
1717
return e.Message
1818
}
1919

20-
// IsValidationError returns true if err is or wraps a ValidationError
21-
func IsValidationError(err error) bool {
22-
if err == nil {
23-
return false
24-
}
25-
_, ok := err.(*ValidationError)
26-
return ok
27-
}
28-
2920
// NewRequiredReleasesError creates a ValidationError indicating that intermediate
3021
// required releases must be installed before upgrading to the target version
3122
func NewRequiredReleasesError(requiredVersions []string, targetVersion string) *ValidationError {

pkg-new/validation/upgradable.go

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"regexp"
7+
"strconv"
78

89
"github.com/Masterminds/semver/v3"
910
"github.com/replicatedhq/embedded-cluster/pkg-new/replicatedapi"
@@ -16,17 +17,70 @@ var k8sBuildRegex = regexp.MustCompile(`k8s-(\d+\.\d+)`)
1617

1718
// UpgradableOptions holds configuration for validating release deployability
1819
type UpgradableOptions struct {
19-
IsAirgap bool
2020
CurrentAppVersion string
2121
CurrentAppSequence int64
2222
CurrentECVersion string
2323
TargetAppVersion string
2424
TargetAppSequence int64
2525
TargetECVersion string
2626
License *kotsv1beta1.License
27-
AirgapMetadata *airgap.AirgapMetadata
28-
ReplicatedAPI replicatedapi.Client
29-
ChannelID string
27+
requiredReleases []string
28+
}
29+
30+
func (opts *UpgradableOptions) WithAirgapRequiredReleases(metadata *airgap.AirgapMetadata) error {
31+
if metadata == nil || metadata.AirgapInfo == nil {
32+
return fmt.Errorf("airgap metadata is required for validating airgap required releases")
33+
}
34+
35+
// RequiredReleases are in descending order, we need to iterate through the required releases of the target release until we find releases lower than the current installed release
36+
requiredReleases := metadata.AirgapInfo.Spec.RequiredReleases
37+
if len(requiredReleases) > 0 {
38+
// Extract version labels from required releases
39+
for _, release := range requiredReleases {
40+
sequence, err := strconv.ParseInt(release.UpdateCursor, 10, 64)
41+
if err != nil {
42+
return fmt.Errorf("failed to parse airgap spec required release update cursor %s: %w", release.UpdateCursor, err)
43+
}
44+
// We've hit a release that is less than or equal to the current installed release, we can stop
45+
if sequence <= opts.CurrentAppSequence {
46+
return nil
47+
}
48+
opts.requiredReleases = append(opts.requiredReleases, release.VersionLabel)
49+
}
50+
}
51+
return nil
52+
}
53+
54+
func (opts *UpgradableOptions) WithOnlineRequiredReleases(ctx context.Context, replAPIClient replicatedapi.Client) error {
55+
if opts.License == nil {
56+
return fmt.Errorf("license is required to check online upgrade required releases")
57+
}
58+
options := &replicatedapi.PendingReleasesOptions{
59+
IsSemverSupported: opts.License.Spec.IsSemverRequired,
60+
SortOrder: replicatedapi.SortOrderAscending,
61+
}
62+
// Get pending releases from the current app sequence in asceding order
63+
pendingReleases, err := replAPIClient.GetPendingReleases(ctx, opts.License.Spec.ChannelID, opts.CurrentAppSequence, options)
64+
if err != nil {
65+
return fmt.Errorf("failed to get pending releases while checking required releases for upgrade: %w", err)
66+
}
67+
if pendingReleases != nil {
68+
opts.handlePendingReleases(pendingReleases.ChannelReleases)
69+
}
70+
return nil
71+
}
72+
73+
func (opts *UpgradableOptions) handlePendingReleases(pendingReleases []replicatedapi.ChannelRelease) {
74+
// Find required releases between current and target sequence
75+
for _, release := range pendingReleases {
76+
// Releases are in asceding order, we've hit the target sequence so we can break
77+
if release.ChannelSequence == opts.TargetAppSequence {
78+
break
79+
}
80+
if release.IsRequired {
81+
opts.requiredReleases = append(opts.requiredReleases, release.VersionLabel)
82+
}
83+
}
3084
}
3185

3286
// ValidateIsReleaseUpgradable validates that a target release can be safely deployed
@@ -56,54 +110,8 @@ func ValidateIsReleaseUpgradable(ctx context.Context, opts UpgradableOptions) er
56110

57111
// validateRequiredReleases checks if any required releases are being skipped
58112
func validateRequiredReleases(ctx context.Context, opts UpgradableOptions) error {
59-
requiredVersions := []string{}
60-
if opts.IsAirgap {
61-
// For airgap, check RequiredReleases field in the airgap metadata
62-
if opts.AirgapMetadata == nil || opts.AirgapMetadata.AirgapInfo == nil {
63-
return fmt.Errorf("airgap metadata is required for airgap validation")
64-
}
65-
66-
requiredReleases := opts.AirgapMetadata.AirgapInfo.Spec.RequiredReleases
67-
if len(requiredReleases) > 0 {
68-
// Extract version labels from required releases
69-
for _, release := range requiredReleases {
70-
requiredVersions = append(requiredVersions, release.VersionLabel)
71-
}
72-
}
73-
} else {
74-
// For online, call the API
75-
if opts.ReplicatedAPI == nil {
76-
return fmt.Errorf("replicated API client is required for online validation")
77-
}
78-
79-
if opts.ChannelID == "" {
80-
return fmt.Errorf("channel ID is required for online validation")
81-
}
82-
83-
options := &replicatedapi.PendingReleasesOptions{
84-
IsSemverSupported: opts.License.Spec.IsSemverRequired,
85-
SortOrder: replicatedapi.SortOrderAscending,
86-
}
87-
// Get pending releases from the current app seqeuence in asceding order
88-
pendingReleases, err := opts.ReplicatedAPI.GetPendingReleases(ctx, opts.ChannelID, opts.CurrentAppSequence, options)
89-
if err != nil {
90-
return fmt.Errorf("failed to fetch pending releases: %w", err)
91-
}
92-
93-
// Find required releases between current and target sequence
94-
for _, release := range pendingReleases.ChannelReleases {
95-
// Releases are in asceding order, we've hit the target sequence so we can break
96-
if release.ChannelSequence == opts.TargetAppSequence {
97-
break
98-
}
99-
if release.IsRequired {
100-
requiredVersions = append(requiredVersions, release.VersionLabel)
101-
}
102-
}
103-
}
104-
105-
if len(requiredVersions) > 0 {
106-
return NewRequiredReleasesError(requiredVersions, opts.TargetAppVersion)
113+
if len(opts.requiredReleases) > 0 {
114+
return NewRequiredReleasesError(opts.requiredReleases, opts.TargetAppVersion)
107115
}
108116

109117
return nil

0 commit comments

Comments
 (0)