From 4eb4a8f064843a02d0983a02106e87300dae7733 Mon Sep 17 00:00:00 2001 From: Stepan Paksashvili Date: Fri, 10 Oct 2025 17:29:20 +0300 Subject: [PATCH 1/2] [chore] ignore absent chart file Signed-off-by: Stepan Paksashvili --- pkg/helm/helm3lib/helm3lib.go | 116 +++++++++++++++++----- pkg/helm/nelm/nelm.go | 49 +++++---- pkg/module_manager/models/modules/helm.go | 14 +-- 3 files changed, 122 insertions(+), 57 deletions(-) diff --git a/pkg/helm/helm3lib/helm3lib.go b/pkg/helm/helm3lib/helm3lib.go index ae873b8c1..59175e6ed 100644 --- a/pkg/helm/helm3lib/helm3lib.go +++ b/pkg/helm/helm3lib/helm3lib.go @@ -6,6 +6,8 @@ import ( "fmt" "log/slog" "os" + "path/filepath" + "slices" "sort" "strconv" "strings" @@ -14,6 +16,7 @@ import ( "github.com/deckhouse/deckhouse/pkg/log" logContext "github.com/deckhouse/deckhouse/pkg/log/context" "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli" @@ -164,15 +167,15 @@ func (h *LibClient) LastReleaseStatus(releaseName string) (string /*revision*/, return strconv.FormatInt(int64(lastRelease.Version), 10), lastRelease.Info.Status.String(), nil } -func (h *LibClient) UpgradeRelease(releaseName string, chartName string, valuesPaths []string, setValues []string, labels map[string]string, namespace string) error { - err := h.upgradeRelease(releaseName, chartName, valuesPaths, setValues, labels, namespace) +func (h *LibClient) UpgradeRelease(releaseName, modulePath string, valuesPaths []string, setValues []string, labels map[string]string, namespace string) error { + err := h.upgradeRelease(releaseName, modulePath, valuesPaths, setValues, labels, namespace) if err != nil { // helm validation can fail because FeatureGate was enabled for example // handling this case we can reinitialize kubeClient and repeat one more time by backoff if err := actionConfigInit(h.Logger); err != nil { return err } - return h.upgradeRelease(releaseName, chartName, valuesPaths, setValues, labels, namespace) + return h.upgradeRelease(releaseName, modulePath, valuesPaths, setValues, labels, namespace) } h.Logger.Debug("helm release upgraded", slog.String("version", releaseName)) return nil @@ -182,7 +185,7 @@ func (h *LibClient) hasLabelsToApply() bool { return len(h.labels) > 0 } -func (h *LibClient) upgradeRelease(releaseName string, chartName string, valuesPaths []string, setValues []string, labels map[string]string, namespace string) error { +func (h *LibClient) upgradeRelease(releaseName, modulePath string, valuesPaths []string, setValues []string, labels map[string]string, namespace string) error { upg := action.NewUpgrade(actionConfig) if namespace != "" { upg.Namespace = namespace @@ -197,11 +200,6 @@ func (h *LibClient) upgradeRelease(releaseName string, chartName string, valuesP upg.Timeout = options.Timeout upg.Labels = labels - chart, err := loader.Load(chartName) - if err != nil { - return err - } - var resultValues chartutil.Values for _, vp := range valuesPaths { @@ -224,9 +222,14 @@ func (h *LibClient) upgradeRelease(releaseName string, chartName string, valuesP resultValues = chartutil.CoalesceTables(resultValues, m) } + loaded, err := loadChart(releaseName, modulePath) + if err != nil { + return err + } + h.Logger.Info("Running helm upgrade for release", slog.String("release", releaseName), - slog.String("chart", chartName), + slog.String("chart", modulePath), slog.String("namespace", namespace)) histClient := action.NewHistory(actionConfig) // Max is not working!!! Sort the final of releases by your own @@ -247,7 +250,7 @@ func (h *LibClient) upgradeRelease(releaseName string, chartName string, valuesP instClient.UseReleaseName = true instClient.Labels = labels - _, err = instClient.Run(chart, resultValues) + _, err = instClient.Run(loaded, resultValues) return err } h.Logger.Debug("old releases found", slog.Int("count", len(releases))) @@ -306,13 +309,13 @@ func (h *LibClient) upgradeRelease(releaseName string, chartName string, valuesP } } - _, err = upg.Run(releaseName, chart, resultValues) + _, err = upg.Run(releaseName, loaded, resultValues) if err != nil { return fmt.Errorf("helm upgrade failed: %s\n", err) } h.Logger.Info("Helm upgrade successful", slog.String("release", releaseName), - slog.String("chart", chartName), + slog.String("chart", modulePath), slog.String("namespace", namespace)) return nil @@ -451,12 +454,7 @@ func (h *LibClient) ListReleasesNames() ([]string, error) { return releases, nil } -func (h *LibClient) Render(releaseName, chartName string, valuesPaths, setValues []string, _ map[string]string, namespace string, debug bool) (string, error) { - chart, err := loader.Load(chartName) - if err != nil { - return "", err - } - +func (h *LibClient) Render(releaseName, modulePath string, valuesPaths, setValues []string, _ map[string]string, namespace string, debug bool) (string, error) { var resultValues chartutil.Values for _, vp := range valuesPaths { @@ -480,19 +478,24 @@ func (h *LibClient) Render(releaseName, chartName string, valuesPaths, setValues } h.Logger.Debug("Render helm templates for chart ...", - slog.String("chart", chartName), + slog.String("chart", modulePath), slog.String("namespace", namespace)) + loaded, err := loadChart(releaseName, modulePath) + if err != nil { + return "", err + } + inst := h.newDryRunInstAction(namespace, releaseName) - rs, err := inst.Run(chart, resultValues) + rs, err := inst.Run(loaded, resultValues) if err != nil { // helm render can fail because the CRD were previously created // handling this case we can reinitialize RESTClient and repeat one more time by backoff _ = actionConfigInit(h.Logger) inst = h.newDryRunInstAction(namespace, releaseName) - rs, err = inst.Run(chart, resultValues) + rs, err = inst.Run(loaded, resultValues) } if err != nil { @@ -506,7 +509,7 @@ func (h *LibClient) Render(releaseName, chartName string, valuesPaths, setValues rs.Manifest += fmt.Sprintf("\n\n\n%v", err) } - h.Logger.Info("Render helm templates for chart was successful", slog.String("chart", chartName)) + h.Logger.Info("Render helm templates for chart was successful", slog.String("chart", modulePath)) return rs.Manifest, nil } @@ -543,3 +546,70 @@ func (h *LibClient) ListReleases() ([]*release.Release, error) { return list, nil } + +func loadChart(moduleName, modulePath string) (*chart.Chart, error) { + if _, err := os.Stat(filepath.Join(modulePath, "Chart.yaml")); err == nil { + return loader.Load(modulePath) + } + + var files []*loader.BufferedFile + + chartYaml := fmt.Sprintf(` +name: %s +version: 0.2.0 +`, moduleName) + + files = append(files, &loader.BufferedFile{ + Name: "Chart.yaml", + Data: []byte(chartYaml), + }) + + ignored := []string{ + "crds", + "docs", + "hooks", + "images", + "lib", + } + + err := filepath.Walk(modulePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + if slices.Contains(ignored, info.Name()) { + return filepath.SkipDir + } + + return nil + } + + relPath, err := filepath.Rel(modulePath, path) + if err != nil { + return err + } + + data, err := os.ReadFile(path) + if err != nil { + return err + } + + files = append(files, &loader.BufferedFile{ + Name: relPath, + Data: data, + }) + + return nil + }) + if err != nil { + return nil, fmt.Errorf("read module files: %w", err) + } + + loaded, err := loader.LoadFiles(files) + if err != nil { + return nil, fmt.Errorf("load chart from files: %w", err) + } + + return loaded, nil +} diff --git a/pkg/helm/nelm/nelm.go b/pkg/helm/nelm/nelm.go index 9d0324662..f1000aa3f 100644 --- a/pkg/helm/nelm/nelm.go +++ b/pkg/helm/nelm/nelm.go @@ -181,10 +181,10 @@ func (c *NelmClient) LastReleaseStatus(releaseName string) (string, string, erro return strconv.FormatInt(int64(releaseGetResult.Release.Revision), 10), releaseGetResult.Release.Status.String(), nil } -func (c *NelmClient) UpgradeRelease(releaseName, chartName string, valuesPaths []string, setValues []string, releaseLabels map[string]string, namespace string) error { +func (c *NelmClient) UpgradeRelease(releaseName, modulePath string, valuesPaths []string, setValues []string, releaseLabels map[string]string, namespace string) error { logger := c.logger.With( slog.String("release_name", releaseName), - slog.String("chart", chartName), + slog.String("chart", modulePath), slog.String("namespace", namespace), ) @@ -220,26 +220,29 @@ func (c *NelmClient) UpgradeRelease(releaseName, chartName string, valuesPaths [ } if err := c.actions.ReleaseInstall(context.TODO(), releaseName, namespace, action.ReleaseInstallOptions{ - Chart: chartName, - ExtraLabels: c.labels, - ExtraAnnotations: extraAnnotations, - KubeContext: c.opts.KubeContext, - NoInstallCRDs: true, - ReleaseHistoryLimit: int(c.opts.HistoryMax), - ReleaseLabels: releaseLabels, - ReleaseStorageDriver: c.opts.HelmDriver, - Timeout: c.opts.Timeout, - ValuesFilesPaths: valuesPaths, - ValuesSets: setValues, - ForceAdoption: true, - NoPodLogs: true, + Chart: modulePath, + DefaultChartName: releaseName, + DefaultChartVersion: "0.2.0", + DefaultChartAPIVersion: "v2", + ExtraLabels: c.labels, + ExtraAnnotations: extraAnnotations, + KubeContext: c.opts.KubeContext, + NoInstallCRDs: true, + ReleaseHistoryLimit: int(c.opts.HistoryMax), + ReleaseLabels: releaseLabels, + ReleaseStorageDriver: c.opts.HelmDriver, + Timeout: c.opts.Timeout, + ValuesFilesPaths: valuesPaths, + ValuesSets: setValues, + ForceAdoption: true, + NoPodLogs: true, }); err != nil { return fmt.Errorf("install nelm release %q: %w", releaseName, err) } logger.Info("Nelm upgrade successful", slog.String("release", releaseName), - slog.String("chart", chartName), + slog.String("chart", modulePath), slog.String("namespace", namespace)) return nil @@ -360,9 +363,9 @@ func (c *NelmClient) ListReleasesNames() ([]string, error) { return releaseNames, nil } -func (c *NelmClient) Render(releaseName, chartName string, valuesPaths, setValues []string, releaseLabels map[string]string, namespace string, debug bool) (string, error) { +func (c *NelmClient) Render(releaseName, modulePath string, valuesPaths, setValues []string, releaseLabels map[string]string, namespace string, debug bool) (string, error) { c.logger.Debug("Render nelm templates for chart ...", - slog.String("chart", chartName), + slog.String("chart", modulePath), slog.String("namespace", namespace)) // Add client annotations @@ -379,7 +382,9 @@ func (c *NelmClient) Render(releaseName, chartName string, valuesPaths, setValue chartRenderResult, err := c.actions.ChartRender(context.TODO(), action.ChartRenderOptions{ OutputFilePath: "/dev/null", // No output file, we want to return the manifest as a string - Chart: chartName, + Chart: modulePath, + DefaultChartName: releaseName, + DefaultChartVersion: "0.2.0", ExtraLabels: c.labels, ExtraAnnotations: extraAnnotations, KubeContext: c.opts.KubeContext, @@ -393,12 +398,12 @@ func (c *NelmClient) Render(releaseName, chartName string, valuesPaths, setValue }) if err != nil { if !debug { - return "", fmt.Errorf("render nelm chart %q: %w\n\nUse --debug flag to render out invalid YAML", chartName, err) + return "", fmt.Errorf("render nelm chart %q: %w\n\nUse --debug flag to render out invalid YAML", modulePath, err) } - return "", fmt.Errorf("render nelm chart %q: %w", chartName, err) + return "", fmt.Errorf("render nelm chart %q: %w", modulePath, err) } - c.logger.Info("Render nelm templates for chart was successful", slog.String("chart", chartName)) + c.logger.Info("Render nelm templates for chart was successful", slog.String("chart", modulePath)) var result strings.Builder for _, resource := range chartRenderResult.Resources { diff --git a/pkg/module_manager/models/modules/helm.go b/pkg/module_manager/models/modules/helm.go index 6f5469fe2..b02ac10ae 100644 --- a/pkg/module_manager/models/modules/helm.go +++ b/pkg/module_manager/models/modules/helm.go @@ -21,7 +21,7 @@ import ( "github.com/flant/addon-operator/pkg" "github.com/flant/addon-operator/pkg/helm" "github.com/flant/addon-operator/pkg/helm/client" - helm3lib "github.com/flant/addon-operator/pkg/helm/helm3lib" + "github.com/flant/addon-operator/pkg/helm/helm3lib" "github.com/flant/addon-operator/pkg/utils" "github.com/flant/kube-client/manifest" "github.com/flant/shell-operator/pkg/utils/measure" @@ -143,7 +143,7 @@ func (hm *HelmModule) isHelmChart() (bool, error) { // check that templates/ dir exists _, err = os.Stat(filepath.Join(hm.path, "templates")) if err == nil { - return true, hm.createChartYaml(chartPath) + return true, nil } if os.IsNotExist(err) { // if templates not exists - it's not a helm module @@ -154,16 +154,6 @@ func (hm *HelmModule) isHelmChart() (bool, error) { return false, err } -func (hm *HelmModule) createChartYaml(chartPath string) error { - // we already have versions like 0.1.0 or 0.1.1 - // to keep helm updatable, we have to increment this version - // new minor version of addon-operator seems reasonable to increase minor version of a helm chart - data := fmt.Sprintf(`name: %s -version: 0.2.0`, hm.name) - - return os.WriteFile(chartPath, []byte(data), 0o644) -} - // checkHelmValues returns error if there is a wrong patch or values are not satisfied // a Helm values contract defined by schemas in 'openapi' directory. func (hm *HelmModule) checkHelmValues() error { From fe43b0d80652d90a99d51fffe1b0dea74b785d28 Mon Sep 17 00:00:00 2001 From: Stepan Paksashvili Date: Fri, 10 Oct 2025 23:35:37 +0300 Subject: [PATCH 2/2] [chore] ignore absent chart file Signed-off-by: Stepan Paksashvili --- pkg/helm/nelm/nelm.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pkg/helm/nelm/nelm.go b/pkg/helm/nelm/nelm.go index f1000aa3f..381e82fe9 100644 --- a/pkg/helm/nelm/nelm.go +++ b/pkg/helm/nelm/nelm.go @@ -381,20 +381,21 @@ func (c *NelmClient) Render(releaseName, modulePath string, valuesPaths, setValu } chartRenderResult, err := c.actions.ChartRender(context.TODO(), action.ChartRenderOptions{ - OutputFilePath: "/dev/null", // No output file, we want to return the manifest as a string - Chart: modulePath, - DefaultChartName: releaseName, - DefaultChartVersion: "0.2.0", - ExtraLabels: c.labels, - ExtraAnnotations: extraAnnotations, - KubeContext: c.opts.KubeContext, - ReleaseName: releaseName, - ReleaseNamespace: namespace, - ReleaseStorageDriver: c.opts.HelmDriver, - Remote: true, - ValuesFilesPaths: valuesPaths, - ValuesSets: setValues, - ForceAdoption: true, + OutputFilePath: "/dev/null", // No output file, we want to return the manifest as a string + Chart: modulePath, + DefaultChartName: releaseName, + DefaultChartVersion: "0.2.0", + DefaultChartAPIVersion: "v2", + ExtraLabels: c.labels, + ExtraAnnotations: extraAnnotations, + KubeContext: c.opts.KubeContext, + ReleaseName: releaseName, + ReleaseNamespace: namespace, + ReleaseStorageDriver: c.opts.HelmDriver, + Remote: true, + ValuesFilesPaths: valuesPaths, + ValuesSets: setValues, + ForceAdoption: true, }) if err != nil { if !debug {