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
1819type 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
58112func 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