-
Notifications
You must be signed in to change notification settings - Fork 7
Enables LCM auto-upgrade #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -97,7 +97,15 @@ func (r *HelmReconciler) reconcileHelmCharts(ctx context.Context, releaseName, r | |
| r.releaseName = releaseName | ||
| r.releaseVersion = releaseVersion | ||
|
|
||
| orderedChartConfigs, err := sortChartConfigsByDependencies(chartConfigs) | ||
| var workloadCharts []*upgrade.HelmChartConfig | ||
| for _, chartCfg := range chartConfigs { | ||
| name := chartCfg.Chart.GetName() | ||
| if !isLCMChart(name) { | ||
| workloadCharts = append(workloadCharts, chartCfg) | ||
| } | ||
| } | ||
|
|
||
| orderedChartConfigs, err := sortChartConfigsByDependencies(workloadCharts) | ||
| if err != nil { | ||
| return &upgrade.PhaseStatus{ | ||
| State: lifecyclev1alpha1.UpgradeFailed, | ||
|
|
@@ -130,7 +138,7 @@ func (r *HelmReconciler) reconcileHelmCharts(ctx context.Context, releaseName, r | |
| } | ||
| } | ||
|
|
||
| return r.aggregateResults(results, len(orderedChartConfigs)), nil | ||
| return aggregateResults(results, len(orderedChartConfigs)), nil | ||
| } | ||
|
|
||
| // sortChartConfigsByDependencies returns a sorted slice of chart configurations, | ||
|
|
@@ -405,7 +413,7 @@ func (r *HelmReconciler) evaluateHelmChartJobStatus(ctx context.Context, chart * | |
| } | ||
|
|
||
| // aggregateResults aggregates chart upgrade results into a single PhaseStatus. | ||
| func (r *HelmReconciler) aggregateResults(results []chartUpgradeResult, totalCharts int) *upgrade.PhaseStatus { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we unbound this from the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sure, I'll revert it. |
||
| func aggregateResults(results []chartUpgradeResult, totalCharts int) *upgrade.PhaseStatus { | ||
| if len(results) == 0 { | ||
| return &upgrade.PhaseStatus{ | ||
| State: lifecyclev1alpha1.UpgradeSucceeded, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| /* | ||
| Copyright © 2026 SUSE LLC | ||
| SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package reconcilers | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| lifecyclev1alpha1 "github.com/suse/elemental-lifecycle-manager/api/v1alpha1" | ||
| "github.com/suse/elemental-lifecycle-manager/internal/helm" | ||
| "github.com/suse/elemental-lifecycle-manager/internal/upgrade" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
| "sigs.k8s.io/controller-runtime/pkg/log" | ||
| ) | ||
|
|
||
| const ( | ||
| ElementalLifecycleManagerChart = "elemental-lifecycle-manager" | ||
| ElementalLifecycleManagerCRDsChart = "elemental-lifecycle-manager-crds" | ||
| ) | ||
|
|
||
| type LCMReconciler struct { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not completely convinced that we need a separate reconciler for LCM. Right now this reconciler is a wrapper over the I wonder, can't we just extend the
I think this is worth considering mainly because:
Do you see value in this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Makes sense! This could likely simplify things. I can modify the PR.
Not a topic for this PR, but even for dirty clusters configuration, we should stick to being strict for LCM charts because that's Elemental project requirement, not user's.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, for specific charts we should always fail. This goes back to how we handle different charts failing, but that is a topic for a different discussion 😄 |
||
| helm *HelmReconciler | ||
| } | ||
|
|
||
| // NewLCMReconciler creates a new LCM reconciler. | ||
| func NewLCMReconciler(c client.Client, h helm.Client) *LCMReconciler { | ||
| return &LCMReconciler{ | ||
| helm: NewHelmReconciler(c, h), | ||
| } | ||
| } | ||
|
|
||
| func (r *LCMReconciler) Phase() upgrade.Phase { | ||
| return upgrade.PhaseLCM | ||
| } | ||
|
|
||
| func (r *LCMReconciler) Reconcile(ctx context.Context, config *upgrade.Config) (*upgrade.PhaseStatus, error) { | ||
| if config == nil || config.HelmCharts == nil { | ||
| return r.Phase().SkippedStatus(), nil | ||
| } | ||
| logger := log.FromContext(ctx) | ||
|
|
||
| r.helm.releaseName = config.ReleaseNamespacedName.Name | ||
| r.helm.releaseVersion = config.ReleaseVersion | ||
|
|
||
| var lcmCharts []*upgrade.HelmChartConfig | ||
| for _, chartConfig := range config.HelmCharts { | ||
| name := chartConfig.Chart.GetName() | ||
| if isLCMChart(name) { | ||
| lcmCharts = append(lcmCharts, chartConfig) | ||
| } | ||
| } | ||
| if len(lcmCharts) == 0 { | ||
| return r.Phase().SkippedStatus(), nil | ||
| } | ||
|
|
||
| orderedChartConfigs, err := sortChartConfigsByDependencies(lcmCharts) | ||
| if err != nil { | ||
| return &upgrade.PhaseStatus{ | ||
| State: lifecyclev1alpha1.UpgradeFailed, | ||
| Message: fmt.Sprintf("Failed to resolve chart dependencies: %v", err), | ||
| }, err | ||
| } | ||
|
|
||
| logger.Info("Reconciling LCM charts and CRDs", "count", len(orderedChartConfigs)) | ||
|
|
||
| var results []chartUpgradeResult | ||
| for _, chartConfig := range orderedChartConfigs { | ||
| name := chartConfig.Chart.GetName() | ||
| state, err := r.helm.reconcileChart(ctx, chartConfig) | ||
| if err != nil { | ||
| return &upgrade.PhaseStatus{ | ||
| State: lifecyclev1alpha1.UpgradeFailed, | ||
| Message: fmt.Sprintf("Failed to reconcile LCM chart %s: %v", name, err), | ||
| }, err | ||
| } | ||
|
|
||
| results = append(results, chartUpgradeResult{ | ||
| chartName: name, | ||
| state: state, | ||
| }) | ||
|
|
||
| if state == helm.ChartStateInProgress { | ||
| logger.Info("LCM chart upgrade in progress, waiting", "chart", name) | ||
| break | ||
| } | ||
|
|
||
| if state == helm.ChartStateFailed { | ||
| // Failure in upgrading either LCM CRD or LCM chart should result in overall upgrade failure, | ||
| // and prevent moving forward to any other upgrade phase | ||
| return &upgrade.PhaseStatus{ | ||
| State: lifecyclev1alpha1.UpgradeFailed, | ||
| Message: fmt.Sprintf("Failed to upgrade LCM chart %q", name), | ||
| }, fmt.Errorf("upgrading LCM chart %q", name) | ||
| } | ||
| } | ||
|
|
||
| return aggregateLCMResults(results, len(orderedChartConfigs)), nil | ||
| } | ||
|
|
||
| // aggregateLCMResults aggregates chart upgrade results into a single PhaseStatus. | ||
| func aggregateLCMResults(results []chartUpgradeResult, totalCharts int) *upgrade.PhaseStatus { | ||
| if len(results) == 0 { | ||
| return &upgrade.PhaseStatus{ | ||
| State: lifecyclev1alpha1.UpgradeSucceeded, | ||
| Message: "No LCM charts to reconcile", | ||
| } | ||
| } | ||
|
|
||
| var inProgress, succeeded, skipped int | ||
|
|
||
| for _, result := range results { | ||
| switch result.state { | ||
| case helm.ChartStateInProgress: | ||
| inProgress++ | ||
| case helm.ChartStateSucceeded, helm.ChartStateVersionAlreadyInstalled: | ||
| succeeded++ | ||
| case helm.ChartStateNotInstalled: | ||
| skipped++ | ||
| } | ||
| } | ||
|
|
||
| if inProgress > 0 { | ||
| return &upgrade.PhaseStatus{ | ||
| State: lifecyclev1alpha1.UpgradeInProgress, | ||
| Message: fmt.Sprintf("LCM charts in progress (%d/%d completed, %d skipped)", succeeded, totalCharts-skipped, skipped), | ||
| } | ||
| } | ||
|
|
||
| if succeeded == 0 && skipped == totalCharts { | ||
| return &upgrade.PhaseStatus{ | ||
| State: lifecyclev1alpha1.UpgradeSucceeded, | ||
| Message: "All LCM charts skipped (not installed on cluster)", | ||
| } | ||
| } | ||
|
|
||
| return &upgrade.PhaseStatus{ | ||
| State: lifecyclev1alpha1.UpgradeSucceeded, | ||
| Message: fmt.Sprintf("All %d LCM charts upgraded successfully (%d skipped)", succeeded, skipped), | ||
| } | ||
| } | ||
|
|
||
| // isLCMChart reports whether name is one of LCM's own charts. These are upgraded by the LCM phase and skipped by the Helm chart phase | ||
| func isLCMChart(name string) bool { | ||
| return name == ElementalLifecycleManagerCRDsChart || name == ElementalLifecycleManagerChart | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: do we actually need to prevent LCMchart to be processed here? Up to my understanding it should not hurt attempting to upgrade it again, the upgrade if it is already at the desired version should do nothing.
If so, then we can probably keep the helm reconciler unmodified and if we ever set an upgrade pipe without LCMreconciler the helm reconciler would still run the full upgrade.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any technical issue in removing
isLCMChartcheck from here. Except it being a brain thing where it feels weird to keep it in spite of having a dedicated phase for it earlier in the flow.If we do this, it would be for the purpose of prevent LCM charts' upgrade, right? But removing
isLCMChartcheck from Helm reconciler would still cause the LCM charts' upgrade. Just presenting a counter point.I'm fine by removing the check from here if it makes more sense. Keeping this open for others' feedback/suggestions.