@@ -16,6 +16,8 @@ import (
1616 "github.com/replicatedhq/embedded-cluster/cmd/installer/goods"
1717 "github.com/replicatedhq/embedded-cluster/cmd/installer/kotscli"
1818 ecv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1"
19+ "github.com/replicatedhq/embedded-cluster/pkg-new/replicatedapi"
20+ "github.com/replicatedhq/embedded-cluster/pkg-new/validation"
1921 "github.com/replicatedhq/embedded-cluster/pkg/airgap"
2022 "github.com/replicatedhq/embedded-cluster/pkg/helpers"
2123 "github.com/replicatedhq/embedded-cluster/pkg/kubeutils"
@@ -59,6 +61,8 @@ type upgradeConfig struct {
5961 managerPort int
6062 requiresInfraUpgrade bool
6163 kotsadmNamespace string
64+ currentAppVersion * kotscli.AppVersionInfo
65+ replicatedAPIClient replicatedapi.Client
6266}
6367
6468// UpgradeCmd returns a cobra command for upgrading the embedded cluster application.
@@ -110,7 +114,7 @@ func UpgradeCmd(ctx context.Context, appSlug, appTitle string) *cobra.Command {
110114 if err := preRunUpgrade (ctx , flags , & upgradeConfig , existingRC , kcli , appSlug ); err != nil {
111115 return err
112116 }
113- if err := verifyAndPromptUpgrade (ctx , flags , upgradeConfig , prompts .New ()); err != nil {
117+ if err := verifyAndPromptUpgrade (ctx , flags , upgradeConfig , prompts .New (), kcli ); err != nil {
114118 return err
115119 }
116120
@@ -254,8 +258,8 @@ func preRunUpgrade(ctx context.Context, flags UpgradeCmdFlags, upgradeConfig *up
254258 }
255259 upgradeConfig .license = l
256260
257- // sync the license if a license is provided and we are not in airgap mode
258- 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 == "" {
259263 replicatedAPI , err := newReplicatedAPIClient (upgradeConfig .license , upgradeConfig .clusterID )
260264 if err != nil {
261265 return fmt .Errorf ("failed to create replicated API client: %w" , err )
@@ -267,6 +271,7 @@ func preRunUpgrade(ctx context.Context, flags UpgradeCmdFlags, upgradeConfig *up
267271 }
268272 upgradeConfig .license = updatedLicense
269273 upgradeConfig .licenseBytes = licenseBytes
274+ upgradeConfig .replicatedAPIClient = replicatedAPI
270275 }
271276
272277 // Continue using "kotsadm" namespace if it exists for backwards compatibility, otherwise use the appSlug
@@ -337,10 +342,17 @@ func preRunUpgrade(ctx context.Context, flags UpgradeCmdFlags, upgradeConfig *up
337342 }
338343 upgradeConfig .requiresInfraUpgrade = requiresInfraUpgrade
339344
345+ // Get current app version for deployability validation
346+ currentAppVersion , err := kotscli .GetCurrentAppVersion (appSlug , upgradeConfig .kotsadmNamespace )
347+ if err != nil {
348+ return fmt .Errorf ("failed to get current app version: %w" , err )
349+ }
350+ upgradeConfig .currentAppVersion = currentAppVersion
351+
340352 return nil
341353}
342354
343- func verifyAndPromptUpgrade (ctx context.Context , flags UpgradeCmdFlags , upgradeConfig upgradeConfig , prompt prompts.Prompt ) error {
355+ func verifyAndPromptUpgrade (ctx context.Context , flags UpgradeCmdFlags , upgradeConfig upgradeConfig , prompt prompts.Prompt , kcli client. Client ) error {
344356 isAirgap := flags .airgapBundle != ""
345357
346358 err := verifyChannelRelease ("upgrade" , isAirgap , flags .assumeYes )
@@ -355,6 +367,16 @@ func verifyAndPromptUpgrade(ctx context.Context, flags UpgradeCmdFlags, upgradeC
355367 }
356368 }
357369
370+ // Validate release upgradable
371+ if err := validateIsReleaseUpgradable (ctx , upgradeConfig , kcli , isAirgap ); err != nil {
372+ var ve * validation.ValidationError
373+ if errors .As (err , & ve ) {
374+ // This is a validation error that prevents the upgrade from proceeding, expose the error directly
375+ return ve
376+ }
377+ return fmt .Errorf ("upgrade validation execution failed: %w" , err )
378+ }
379+
358380 if ! isAirgap {
359381 if err := maybePromptForAppUpdate (ctx , prompt , upgradeConfig .license , flags .assumeYes ); err != nil {
360382 if errors .As (err , & ErrorNothingElseToAdd {}) {
@@ -554,3 +576,67 @@ func checkRequiresInfraUpgrade(ctx context.Context) (bool, error) {
554576
555577 return ! bytes .Equal (currentJSON , targetJSON ), nil
556578}
579+
580+ // validateIsReleaseUpgradable validates that the target release can be safely deployed
581+ func validateIsReleaseUpgradable (ctx context.Context , upgradeConfig upgradeConfig , kcli client.Client , isAirgap bool ) error {
582+ // Get current installation for version information
583+ currentInstallation , err := kubeutils .GetLatestInstallation (ctx , kcli )
584+ if err != nil {
585+ return fmt .Errorf ("get current installation: %w" , err )
586+ }
587+
588+ // Get target release data
589+ releaseData := release .GetReleaseData ()
590+ if releaseData == nil {
591+ return fmt .Errorf ("release data not found" )
592+ }
593+
594+ // Get channel release info
595+ channelRelease := releaseData .ChannelRelease
596+ if channelRelease == nil {
597+ return fmt .Errorf ("channel release not found in release data" )
598+ }
599+
600+ // Get current and target EC/K8s versions
601+ var currentECVersion string
602+ if currentInstallation .Spec .Config != nil {
603+ currentECVersion = currentInstallation .Spec .Config .Version
604+ }
605+
606+ targetECVersion := versions .Version
607+
608+ // Build validation options
609+ opts := validation.UpgradableOptions {
610+ CurrentECVersion : currentECVersion ,
611+ TargetECVersion : targetECVersion ,
612+ License : upgradeConfig .license ,
613+ }
614+
615+ // Add current app version info if available
616+ if upgradeConfig .currentAppVersion != nil {
617+ opts .CurrentAppVersion = upgradeConfig .currentAppVersion .VersionLabel
618+ opts .CurrentAppSequence = upgradeConfig .currentAppVersion .ChannelSequence
619+ }
620+
621+ // Add target app version info
622+ opts .TargetAppVersion = channelRelease .VersionLabel
623+ opts .TargetAppSequence = channelRelease .ChannelSequence
624+
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 replicated API's pending release call: %w" , err )
633+ }
634+ }
635+
636+ // Perform validation
637+ if err := validation .ValidateIsReleaseUpgradable (ctx , opts ); err != nil {
638+ return err
639+ }
640+
641+ return nil
642+ }
0 commit comments