diff --git a/cmd/apply.go b/cmd/apply.go index 7da3a7ec..6bb4d5e1 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -59,10 +59,14 @@ type ClusterCmdFlags struct { UpgradePathLocation string UpgradeNode string DistroPatchesLocation string + PostApplyPhases []string ClusterSkipsCmdFlags } -var ErrDownloadDependenciesFailed = errors.New("dependencies download failed") +var ( + ErrDownloadDependenciesFailed = errors.New("dependencies download failed") + ErrPhaseInvalid = errors.New("phase is not valid") +) func NewApplyCmd() *cobra.Command { var cmdEvent analytics.Event @@ -236,6 +240,7 @@ func NewApplyCmd() *cobra.Command { flags.Upgrade, flags.UpgradePathLocation, flags.UpgradeNode, + flags.PostApplyPhases, ) if err != nil { cmdEvent.AddErrorMessage(err) @@ -328,6 +333,12 @@ func getCreateClusterCmdFlags() (ClusterCmdFlags, error) { ) } + postApplyPhases := viper.GetStringSlice("post-apply-phases") + + if err := validatePostApplyPhasesFlag(postApplyPhases); err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s %w", ErrParsingFlag, "post-apply-phases", err) + } + return ClusterCmdFlags{ Debug: viper.GetBool("debug"), FuryctlPath: viper.GetString("config"), @@ -350,9 +361,20 @@ func getCreateClusterCmdFlags() (ClusterCmdFlags, error) { UpgradeNode: upgradeNode, DistroPatchesLocation: viper.GetString("distro-patches"), ClusterSkipsCmdFlags: skips, + PostApplyPhases: postApplyPhases, }, nil } +func validatePostApplyPhasesFlag(phases []string) error { + for _, phase := range phases { + if err := cluster.ValidateMainPhases(phase); err != nil { + return fmt.Errorf("%w: %s", ErrPhaseInvalid, phase) + } + } + + return nil +} + func setupCreateClusterCmdFlags(cmd *cobra.Command) { cmd.Flags().StringP( "config", @@ -446,6 +468,12 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { "WARNING: furyctl won't ask for confirmation and will proceed applying upgrades and reducers. Options are: all, upgrades, migrations, pods-running-check", ) + cmd.Flags().StringSlice( + "post-apply-phases", + []string{}, + "Phases to run after the apply command. Options are: infrastructure, kubernetes, distribution, plugins", + ) + cmd.Flags().Int( "timeout", 3600, //nolint:mnd,revive // ignore magic number linters diff --git a/cmd/diff.go b/cmd/diff.go index a056da37..de3c999d 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -257,6 +257,7 @@ func getPhasePath( true, upgradePathLocation, "", + []string{}, ) if err != nil { return "", fmt.Errorf("error while initializing cluster creator: %w", err) diff --git a/internal/apis/kfd/v1alpha2/ekscluster/create/distribution.go b/internal/apis/kfd/v1alpha2/ekscluster/create/distribution.go index de28d3f3..b204ae80 100644 --- a/internal/apis/kfd/v1alpha2/ekscluster/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/ekscluster/create/distribution.go @@ -377,6 +377,10 @@ func (d *Distribution) runReducers( return nil } +func (d *Distribution) SetUpgrade(upgradeEnabled bool) { + d.upgrade.Enabled = upgradeEnabled +} + func (d *Distribution) Stop() error { errCh := make(chan error) doneCh := make(chan bool) diff --git a/internal/apis/kfd/v1alpha2/ekscluster/create/infrastructure.go b/internal/apis/kfd/v1alpha2/ekscluster/create/infrastructure.go index 896a7956..fc74dfeb 100644 --- a/internal/apis/kfd/v1alpha2/ekscluster/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/ekscluster/create/infrastructure.go @@ -215,6 +215,10 @@ func (i *Infrastructure) postInfrastructure( return nil } +func (i *Infrastructure) SetUpgrade(upgradeEnabled bool) { + i.upgrade.Enabled = upgradeEnabled +} + func (i *Infrastructure) Stop() error { logrus.Debug("Stopping terraform...") diff --git a/internal/apis/kfd/v1alpha2/ekscluster/create/kubernetes.go b/internal/apis/kfd/v1alpha2/ekscluster/create/kubernetes.go index 71732b51..c391bc2e 100644 --- a/internal/apis/kfd/v1alpha2/ekscluster/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/ekscluster/create/kubernetes.go @@ -282,6 +282,10 @@ func (k *Kubernetes) postKubernetes( return nil } +func (k *Kubernetes) SetUpgrade(upgradeEnabled bool) { + k.upgrade.Enabled = upgradeEnabled +} + func (k *Kubernetes) Stop() error { errCh := make(chan error) doneCh := make(chan bool) diff --git a/internal/apis/kfd/v1alpha2/ekscluster/creator.go b/internal/apis/kfd/v1alpha2/ekscluster/creator.go index ea693b8e..44fc3043 100644 --- a/internal/apis/kfd/v1alpha2/ekscluster/creator.go +++ b/internal/apis/kfd/v1alpha2/ekscluster/creator.go @@ -60,6 +60,7 @@ type ClusterCreator struct { force []string upgrade bool externalUpgradesPath string + postApplyPhases []string } type Phases struct { @@ -158,6 +159,11 @@ func (v *ClusterCreator) SetProperty(name string, value any) { if s, ok := value.(string); ok { v.externalUpgradesPath = s } + + case cluster.CreatorPropertyPostApplyPhases: + if s, ok := value.([]string); ok { + v.postApplyPhases = s + } } } @@ -637,6 +643,14 @@ func (v *ClusterCreator) allPhases( return err } + if len(v.postApplyPhases) > 0 { + logrus.Info("Executing extra phases...") + + if err := v.extraPhases(phases, upgradeState, upgr); err != nil { + return fmt.Errorf("error while executing extra phases: %w", err) + } + } + if v.dryRun { logrus.Info("Kubernetes Fury cluster created successfully (dry-run mode)") @@ -670,6 +684,52 @@ func (v *ClusterCreator) allPhases( return nil } +func (v *ClusterCreator) extraPhases(phases *Phases, upgradeState *upgrade.State, upgr *upgrade.Upgrade) error { + initialUpgrade := upgr.Enabled + + defer func() { + upgr.Enabled = initialUpgrade + }() + + for _, phase := range v.postApplyPhases { + switch phase { + case cluster.OperationPhaseInfrastructure: + phases.Infrastructure.SetUpgrade(false) + + if err := phases.Infrastructure.Exec(StartFromFlagNotSet, upgradeState); err != nil { + return fmt.Errorf("error while executing post infrastructure phase: %w", err) + } + + case cluster.OperationPhaseKubernetes: + phases.Kubernetes.SetUpgrade(false) + + if err := phases.Kubernetes.Exec(StartFromFlagNotSet, upgradeState); err != nil { + return fmt.Errorf("error while executing post kubernetes phase: %w", err) + } + + case cluster.OperationPhaseDistribution: + phases.Distribution.SetUpgrade(false) + + if err := phases.Distribution.Exec( + reducers.Reducers{}, + StartFromFlagNotSet, + upgradeState, + ); err != nil { + return fmt.Errorf("error while executing post distribution phase: %w", err) + } + + case cluster.OperationPhasePlugins: + if distribution.HasFeature(v.kfdManifest, distribution.FeaturePlugins) { + if err := phases.Plugins.Exec(); err != nil { + return fmt.Errorf("error while executing plugins phase: %w", err) + } + } + } + } + + return nil +} + func (v *ClusterCreator) allPhasesExec( startFrom string, phases *Phases, diff --git a/internal/apis/kfd/v1alpha2/kfddistribution/create/distribution.go b/internal/apis/kfd/v1alpha2/kfddistribution/create/distribution.go index 5f5f0403..ba95e7fb 100644 --- a/internal/apis/kfd/v1alpha2/kfddistribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/kfddistribution/create/distribution.go @@ -364,6 +364,10 @@ func (d *Distribution) Stop() error { return nil } +func (d *Distribution) SetUpgrade(upgradeEnabled bool) { + d.upgrade.Enabled = upgradeEnabled +} + func (d *Distribution) runReducers( rdcs reducers.Reducers, cfg template.Config, diff --git a/internal/apis/kfd/v1alpha2/kfddistribution/creator.go b/internal/apis/kfd/v1alpha2/kfddistribution/creator.go index fe4a665c..481c9274 100644 --- a/internal/apis/kfd/v1alpha2/kfddistribution/creator.go +++ b/internal/apis/kfd/v1alpha2/kfddistribution/creator.go @@ -49,6 +49,7 @@ type ClusterCreator struct { force []string upgrade bool externalUpgradesPath string + postApplyPhases []string } func (c *ClusterCreator) SetProperties(props []cluster.CreatorProperty) { @@ -127,6 +128,11 @@ func (c *ClusterCreator) SetProperty(name string, value any) { if s, ok := value.(string); ok { c.externalUpgradesPath = s } + + case cluster.CreatorPropertyPostApplyPhases: + if s, ok := value.([]string); ok { + c.postApplyPhases = s + } } } @@ -366,6 +372,47 @@ func (c *ClusterCreator) allPhases( } } + if len(c.postApplyPhases) > 0 { + logrus.Info("Executing extra phases...") + + if err := c.extraPhases(distributionPhase, pluginsPhase, upgradeState, upgr); err != nil { + return fmt.Errorf("error while executing extra phases: %w", err) + } + } + + return nil +} + +func (c *ClusterCreator) extraPhases( + distributionPhase upgrade.ReducersOperatorPhase[reducers.Reducers], + pluginsPhase *commcreate.Plugins, + upgradeState *upgrade.State, + upgr *upgrade.Upgrade, +) error { + initialUpgrade := upgr.Enabled + + defer func() { + upgr.Enabled = initialUpgrade + }() + + for _, phase := range c.postApplyPhases { + switch phase { + case cluster.OperationPhaseDistribution: + distributionPhase.SetUpgrade(false) + + if err := distributionPhase.Exec(reducers.Reducers{}, StartFromFlagNotSet, upgradeState); err != nil { + return fmt.Errorf("error while executing distribution phase: %w", err) + } + + case cluster.OperationPhasePlugins: + if distribution.HasFeature(c.kfdManifest, distribution.FeaturePlugins) { + if err := pluginsPhase.Exec(); err != nil { + return fmt.Errorf("error while executing plugins phase: %w", err) + } + } + } + } + return nil } diff --git a/internal/apis/kfd/v1alpha2/onpremises/create/distribution.go b/internal/apis/kfd/v1alpha2/onpremises/create/distribution.go index 6a4faa56..368957da 100644 --- a/internal/apis/kfd/v1alpha2/onpremises/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/onpremises/create/distribution.go @@ -289,6 +289,10 @@ func (d *Distribution) injectStoredConfig(cfg template.Config) (template.Config, return cfg, nil } +func (d *Distribution) SetUpgrade(upgradeEnabled bool) { + d.upgrade.Enabled = upgradeEnabled +} + func NewDistribution( furyctlConf public.OnpremisesKfdV1Alpha2, kfdManifest config.KFD, diff --git a/internal/apis/kfd/v1alpha2/onpremises/create/kubernetes.go b/internal/apis/kfd/v1alpha2/onpremises/create/kubernetes.go index 6315adf1..24278de7 100644 --- a/internal/apis/kfd/v1alpha2/onpremises/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/onpremises/create/kubernetes.go @@ -212,6 +212,10 @@ func (k *Kubernetes) postKubernetes( return nil } +func (k *Kubernetes) SetUpgrade(upgradeEnabled bool) { + k.upgrade.Enabled = upgradeEnabled +} + func NewKubernetes( furyctlConf public.OnpremisesKfdV1Alpha2, kfdManifest config.KFD, diff --git a/internal/apis/kfd/v1alpha2/onpremises/creator.go b/internal/apis/kfd/v1alpha2/onpremises/creator.go index 92248245..af075c31 100644 --- a/internal/apis/kfd/v1alpha2/onpremises/creator.go +++ b/internal/apis/kfd/v1alpha2/onpremises/creator.go @@ -52,6 +52,7 @@ type ClusterCreator struct { upgrade bool externalUpgradesPath string upgradeNode string + postApplyPhases []string } func (c *ClusterCreator) SetProperties(props []cluster.CreatorProperty) { @@ -140,6 +141,11 @@ func (c *ClusterCreator) SetProperty(name string, value any) { if s, ok := value.(string); ok { c.upgradeNode = s } + + case cluster.CreatorPropertyPostApplyPhases: + if s, ok := value.([]string); ok { + c.postApplyPhases = s + } } } @@ -427,6 +433,61 @@ func (c *ClusterCreator) allPhases( } } + if len(c.postApplyPhases) > 0 { + logrus.Info("Executing extra phases...") + + if err := c.extraPhases( + kubernetesPhase, + distributionPhase, + pluginsPhase, + upgr, + upgradeState, + ); err != nil { + return fmt.Errorf("error while executing extra phases: %w", err) + } + } + + return nil +} + +func (c *ClusterCreator) extraPhases( + kubernetesPhase upgrade.OperatorPhase, + distributionPhase upgrade.ReducersOperatorPhase[reducers.Reducers], + pluginsPhase *commcreate.Plugins, + upgr *upgrade.Upgrade, + upgradeState *upgrade.State, +) error { + initialUpgrade := upgr.Enabled + + defer func() { + upgr.Enabled = initialUpgrade + }() + + for _, phase := range c.postApplyPhases { + switch phase { + case cluster.OperationPhaseKubernetes: + kubernetesPhase.SetUpgrade(false) + + if err := kubernetesPhase.Exec(StartFromFlagNotSet, upgradeState); err != nil { + return fmt.Errorf("error while executing kubernetes phase: %w", err) + } + + case cluster.OperationPhaseDistribution: + distributionPhase.SetUpgrade(false) + + if err := distributionPhase.Exec(nil, StartFromFlagNotSet, upgradeState); err != nil { + return fmt.Errorf("error while executing distribution phase: %w", err) + } + + case cluster.OperationPhasePlugins: + if distribution.HasFeature(c.kfdManifest, distribution.FeaturePlugins) { + if err := pluginsPhase.Exec(); err != nil { + return fmt.Errorf("error while executing plugins phase: %w", err) + } + } + } + } + return nil } diff --git a/internal/cluster/creator.go b/internal/cluster/creator.go index ccf99ea1..d2989cbf 100644 --- a/internal/cluster/creator.go +++ b/internal/cluster/creator.go @@ -29,6 +29,7 @@ const ( CreatorPropertyUpgrade = "upgrade" CreatorPropertyExternalUpgradesPath = "externalupgradespath" CreatorPropertyUpgradeNode = "upgradenode" + CreatorPropertyPostApplyPhases = "postapplyphases" ) var ( @@ -72,6 +73,7 @@ func NewCreator( upgrade bool, externalUpgradesPath, upgradeNode string, + postApplyPhases []string, ) (Creator, error) { lcAPIVersion := strings.ToLower(minimalConf.APIVersion) lcResourceType := strings.ToLower(minimalConf.Kind) @@ -130,6 +132,10 @@ func NewCreator( Name: CreatorPropertyUpgradeNode, Value: upgradeNode, }, + { + Name: CreatorPropertyPostApplyPhases, + Value: postApplyPhases, + }, }) } diff --git a/internal/cluster/phase.go b/internal/cluster/phase.go index 3167b2ad..fed54422 100644 --- a/internal/cluster/phase.go +++ b/internal/cluster/phase.go @@ -88,6 +88,19 @@ func ValidateOperationPhase(phase string) error { } } +func ValidateMainPhases(phase string) error { + switch phase { + case OperationPhaseInfrastructure, + OperationPhaseKubernetes, + OperationPhaseDistribution, + OperationPhasePlugins: + return nil + + default: + return ErrUnsupportedPhase + } +} + func GetPhasesOrder() []string { return []string{ "PreInfrastructure", diff --git a/internal/upgrade/storedecorator.go b/internal/upgrade/storedecorator.go index b91b250a..c51e0263 100644 --- a/internal/upgrade/storedecorator.go +++ b/internal/upgrade/storedecorator.go @@ -14,6 +14,7 @@ type ( Reducers = any ReducersOperatorPhase[T Reducers] interface { Exec(reducers T, startFrom string, upgradeState *State) error + SetUpgrade(upgradeEnabled bool) Self() *cluster.OperationPhase } ) @@ -66,6 +67,10 @@ func (d *ReducerOperatorPhaseDecorator[T]) Exec(reducers T, startFrom string, up return nil } +func (d *ReducerOperatorPhaseDecorator[T]) SetUpgrade(upgradeEnabled bool) { + d.upgr.Enabled = upgradeEnabled +} + func (d *ReducerOperatorPhaseDecorator[T]) Self() *cluster.OperationPhase { return d.phase.Self() } @@ -113,6 +118,10 @@ func (d *ReducerOperatorPhaseAsyncDecorator[T]) Exec(reducers T, startFrom strin return nil } +func (d *ReducerOperatorPhaseAsyncDecorator[T]) SetUpgrade(upgradeEnabled bool) { //nolint: lll,revive // confusing-naming is a false positive + d.upgr.Enabled = upgradeEnabled +} + func (d *ReducerOperatorPhaseAsyncDecorator[T]) Stop() error { if err := d.phase.Stop(); err != nil { return fmt.Errorf("error while stopping phase: %w", err) @@ -128,6 +137,7 @@ func (d *ReducerOperatorPhaseAsyncDecorator[T]) Self() *cluster.OperationPhase { type OperatorPhase interface { Exec(startFrom string, upgradeState *State) error Self() *cluster.OperationPhase + SetUpgrade(upgradeEnabled bool) } type OperatorPhaseAsync interface { @@ -178,6 +188,10 @@ func (d *OperatorPhaseDecorator) Exec(startFrom string, upgradeState *State) err return nil } +func (d *OperatorPhaseDecorator) SetUpgrade(upgradeEnabled bool) { + d.upgr.Enabled = upgradeEnabled +} + func (d *OperatorPhaseDecorator) Self() *cluster.OperationPhase { return d.phase.Self() } @@ -233,6 +247,10 @@ func (d *OperatorPhaseAsyncDecorator) Stop() error { return nil } +func (d *OperatorPhaseAsyncDecorator) SetUpgrade(upgradeEnabled bool) { + d.upgr.Enabled = upgradeEnabled +} + func (d *OperatorPhaseAsyncDecorator) Self() *cluster.OperationPhase { return d.phase.Self() }