Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package nelm for openSUSE:Factory checked in 
at 2025-05-14 17:01:59
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/nelm (Old)
 and      /work/SRC/openSUSE:Factory/.nelm.new.30101 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "nelm"

Wed May 14 17:01:59 2025 rev:7 rq:1277395 version:1.4.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/nelm/nelm.changes        2025-05-07 
19:21:52.332723273 +0200
+++ /work/SRC/openSUSE:Factory/.nelm.new.30101/nelm.changes     2025-05-14 
17:02:34.046655715 +0200
@@ -1,0 +2,15 @@
+Wed May 14 10:43:53 UTC 2025 - Johannes Kastl 
<[email protected]>
+
+- Update to version 1.4.0:
+  * Features
+    - --no-install-crds for release install/plan (efc22bc)
+    - --print-values option for release get (f41f615)
+    - --release-labels option for release install (9b20bc0)
+    - --timeout option for release install/rollback/uninstall/plan
+      (d563296)
+  * Bug Fixes
+    - disallow unknown NELM_FEAT_.* env vars (7e25a16)
+  * Dependencies
+    - chore: update 3p-helm module
+
+-------------------------------------------------------------------

Old:
----
  nelm-1.3.0.obscpio

New:
----
  nelm-1.4.0.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ nelm.spec ++++++
--- /var/tmp/diff_new_pack.Hz7Vjg/_old  2025-05-14 17:02:35.286707789 +0200
+++ /var/tmp/diff_new_pack.Hz7Vjg/_new  2025-05-14 17:02:35.286707789 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           nelm
-Version:        1.3.0
+Version:        1.4.0
 Release:        0
 Summary:        Helm 3 alternative
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.Hz7Vjg/_old  2025-05-14 17:02:35.330709638 +0200
+++ /var/tmp/diff_new_pack.Hz7Vjg/_new  2025-05-14 17:02:35.334709806 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/werf/nelm</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v1.3.0</param>
+    <param name="revision">v1.4.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.Hz7Vjg/_old  2025-05-14 17:02:35.358710814 +0200
+++ /var/tmp/diff_new_pack.Hz7Vjg/_new  2025-05-14 17:02:35.362710982 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/werf/nelm</param>
-              <param 
name="changesrevision">d2d74179b19089543e60d302f74e969f6cbe8d6c</param></service></servicedata>
+              <param 
name="changesrevision">bbc21f6c6e7bc0b7b7791fd575a3899a17dda2b5</param></service></servicedata>
 (No newline at EOF)
 

++++++ nelm-1.3.0.obscpio -> nelm-1.4.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/CHANGELOG.md new/nelm-1.4.0/CHANGELOG.md
--- old/nelm-1.3.0/CHANGELOG.md 2025-05-07 12:46:15.000000000 +0200
+++ new/nelm-1.4.0/CHANGELOG.md 2025-05-14 11:22:12.000000000 +0200
@@ -1,5 +1,20 @@
 # Changelog
 
+## [1.4.0](https://www.github.com/werf/nelm/compare/v1.3.0...v1.4.0) 
(2025-05-14)
+
+
+### Features
+
+* `--no-install-crds` for `release install/plan` 
([efc22bc](https://www.github.com/werf/nelm/commit/efc22bca73d07af229c351ca74f3e29ebb571f44))
+* `--print-values` option for `release get` 
([f41f615](https://www.github.com/werf/nelm/commit/f41f615b3917da22d333d6d588b7b7adf2f9505e))
+* `--release-labels` option for `release install` 
([9b20bc0](https://www.github.com/werf/nelm/commit/9b20bc0c63e8651ce3cd728fcf9d8d471da12652))
+* `--timeout` option for `release install/rollback/uninstall/plan` 
([d563296](https://www.github.com/werf/nelm/commit/d563296ac4866f3d9ec0030308acb7f9ef20211a))
+
+
+### Bug Fixes
+
+* disallow unknown NELM_FEAT_.* env vars 
([7e25a16](https://www.github.com/werf/nelm/commit/7e25a16f5f0c40d94308c77d18cad5cee31d5194))
+
 ## [1.3.0](https://www.github.com/werf/nelm/compare/v1.2.2...v1.3.0) 
(2025-05-07)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/README.md new/nelm-1.4.0/README.md
--- old/nelm-1.3.0/README.md    2025-05-07 12:46:15.000000000 +0200
+++ new/nelm-1.4.0/README.md    2025-05-14 11:22:12.000000000 +0200
@@ -9,7 +9,7 @@
 * lots of fixes for Helm 3 bugs, e.g. ["no matches for kind Deployment in 
version apps/v1beta1"](https://github.com/helm/helm/issues/7219);
 * ... and more.
 
-**Note on the project status and production readiness**. Prior to a formal 
public announcement, thousands of projects actively used Nelm via werf. Being 
the default and only deployment engine since werf v2.0 
([released](https://github.com/werf/werf/discussions/6100) in April 2024), Nelm 
and all its main features were battle-tested in production before a separate 
tool with user-facing CLI emerged. However, the Nelm *CLI* [went 
public](https://blog.werf.io/nelm-cli-helm-compatible-alternative-5648b191f0af) 
with the Nelm v1 release only (in April 2025) and hasn’t been tested that much 
yet.
+We consider Nelm production-ready, since 95% of the Nelm codebase basically is 
the werf deployment engine, which was battle-tested in production across 
thousands of projects for years.
 
 ## Table of Contents
 
@@ -27,6 +27,7 @@
   - [Printing logs and events during 
deploy](#printing-logs-and-events-during-deploy)
   - [Release planning](#release-planning)
   - [Encrypted values and encrypted 
files](#encrypted-values-and-encrypted-files)
+  - [Improved CRD management](#improved-crd-management)
 - [Documentation](#documentation)
   - [Usage](#usage)
     - [Encrypted values files](#encrypted-values-files)
@@ -48,8 +49,10 @@
     - [Annotation 
`werf.io/show-logs-only-for-containers`](#annotation-werfioshow-logs-only-for-containers)
     - [Annotation 
`werf.io/show-service-messages`](#annotation-werfioshow-service-messages)
     - [Function `werf_secret_file`](#function-werf_secret_file)
+  - [Feature gates](#feature-gates)
+    - [Env variable 
`NELM_FEAT_REMOTE_CHARTS`](#env-variable-nelm_feat_remote_charts)
   - [More information](#more-information)
-- [Known issues](#known-issues)
+- [Limitations](#limitations)
 - [Future plans](#future-plans)
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -296,6 +299,10 @@
 
 `nelm chart secret` commands manage encrypted values files such as 
`secret-values.yaml` or encrypted arbitrary files like `secret/mysecret.txt`. 
These files are decrypted in-memory during templating and can be used in 
templates as `.Values.my.secret.value` and `{{ werf_secret_file "mysecret.txt" 
}}`, respectively.
 
+### Improved CRD management
+
+CRDs from the `crds/` directory of the chart deployed not only on the very 
first release install, but also on release upgrades. Also, CRDs not only can be 
created, but can be updated as well.
+
 ## Documentation
 
 Nelm-specific features are described below. For general documentation, see 
[Helm docs](https://helm.sh/docs/) and [werf 
docs](https://werf.io/docs/v2/usage/deploy/overview.html).
@@ -499,21 +506,35 @@
 
 Read the specified secret file from the `secret/` directory of the Helm chart.
 
+### Feature gates
+
+#### Env variable `NELM_FEAT_REMOTE_CHARTS`
+
+Example:
+```shell
+export NELM_FEAT_REMOTE_CHARTS=true
+nelm release install -n myproject -r myproject --chart-version 19.1.1 
bitnami/nginx
+```
+
+Allows specifying not only local, but also remote charts as a command-line 
argument to commands such as `nelm release install`. Adds the `--chart-version` 
option as well.
+
 ### More information
 
 For more information, see [Helm docs](https://helm.sh/docs/) and [werf 
docs](https://werf.io/docs/v2/usage/deploy/overview.html).
 
-## Known issues
+## Limitations
 
-- Nelm won't work with Kubernetes versions earlier than v1.14. The 
`ServerSideApply` feature gate should be enabled (it's enabled by default 
starting from Kubernetes v1.16). This requirement is caused by leveraging the 
Server-Side Apply (instead of 3-Way Merge in Helm).
+* Nelm requires Server-Side Apply enabled in Kubernetes. It is enabled by 
default since Kubernetes 1.16. In Kubernetes 1.14-1.15 it can be enabled, but 
disabled by default. Kubernetes 1.13 and older doesn't have Server-Side Apply, 
thus Nelm won't work with it.
+* Helm *sometimes* uses Values from the previous Helm release to deploy a new 
release. This "feature" meant to make using Helm without a proper CI/CD process 
easier. This is dangerous, goes against IaC and this is not what most users 
expect. Nelm will *never* use Values from previous Helm releases for any 
purposes. What you explicitly pass via `--values` and `--set` options will be 
merged with builtin chart Values, then applied to the cluster. Thus, options 
`--reuse-values`, `--reset-values`, `--reset-then-reuse-values` will not be 
implemented.
 
 ## Future plans
 
-Here are some of the big things we plan to implement — feel free to upvote or 
comment on the relevant issues and/or raise new ones to show your interest:
+Here are some of the big things we plan to implement — upvote or comment 
relevant issues or create a new one to help us prioritize:
 
-- An alternative to Helm templating 
([#54](https://github.com/werf/nelm/issues/54));
-- An option to pull charts directly from Git;
-- A public Go API for embedding Nelm into third-party software;
-- Enhance the CLI experience with new commands and improve the consistency 
between the reimplemented commands and original Helm commands;
-- Overhaul the chart dependency management 
([#61](https://github.com/werf/nelm/issues/61));
-- Migrate the built-in secret management to Mozilla SOPS 
([#62](https://github.com/werf/nelm/issues/62)).
+* The Nelm operator, interoperable with ArgoCD/Flux.
+* An alternative to Helm templating 
([#54](https://github.com/werf/nelm/issues/54)).
+* An option to pull charts directly from Git.
+* A public Go API for embedding Nelm into third-party software.
+* Enhance the CLI experience with new commands and improve the consistency 
between the reimplemented commands and original Helm commands.
+* Overhaul the chart dependency management 
([#61](https://github.com/werf/nelm/issues/61)).
+* Migrate the built-in secret management to Mozilla SOPS 
([#62](https://github.com/werf/nelm/issues/62)).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/cmd/nelm/chart_lint.go 
new/nelm-1.4.0/cmd/nelm/chart_lint.go
--- old/nelm-1.3.0/cmd/nelm/chart_lint.go       2025-05-07 12:46:15.000000000 
+0200
+++ new/nelm-1.4.0/cmd/nelm/chart_lint.go       2025-05-14 11:22:12.000000000 
+0200
@@ -23,7 +23,7 @@
        cfg := &chartLintConfig{}
 
        use := "lint [options...]"
-       if featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+       if featgate.FeatGateRemoteCharts.Enabled() {
                use += " 
[chart-dir|chart-repo-name/chart-name|chart-archive|chart-archive-url]"
        } else {
                use += " [chart-dir]"
@@ -48,7 +48,7 @@
                        })
 
                        if len(args) > 0 {
-                               if 
featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+                               if featgate.FeatGateRemoteCharts.Enabled() {
                                        cfg.Chart = args[0]
                                } else {
                                        cfg.ChartDirPath = args[0]
@@ -92,7 +92,7 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
-               if featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+               if featgate.FeatGateRemoteCharts.Enabled() {
                        if err := cli.AddFlag(cmd, &cfg.ChartVersion, 
"chart-version", "", "Choose a remote chart version, otherwise the latest 
version is used", cli.AddFlagOptions{
                                GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
                                Group:                mainFlagGroup,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/cmd/nelm/chart_render.go 
new/nelm-1.4.0/cmd/nelm/chart_render.go
--- old/nelm-1.3.0/cmd/nelm/chart_render.go     2025-05-07 12:46:15.000000000 
+0200
+++ new/nelm-1.4.0/cmd/nelm/chart_render.go     2025-05-14 11:22:12.000000000 
+0200
@@ -23,7 +23,7 @@
        cfg := &chartRenderConfig{}
 
        use := "render [options...]"
-       if featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+       if featgate.FeatGateRemoteCharts.Enabled() {
                use += " 
[chart-dir|chart-repo-name/chart-name|chart-archive|chart-archive-url]"
        } else {
                use += " [chart-dir]"
@@ -49,14 +49,14 @@
                        })
 
                        if len(args) > 0 {
-                               if 
featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+                               if featgate.FeatGateRemoteCharts.Enabled() {
                                        cfg.Chart = args[0]
                                } else {
                                        cfg.ChartDirPath = args[0]
                                }
                        }
 
-                       if err := action.ChartRender(ctx, 
cfg.ChartRenderOptions); err != nil {
+                       if _, err := action.ChartRender(ctx, 
cfg.ChartRenderOptions); err != nil {
                                return fmt.Errorf("chart render: %w", err)
                        }
 
@@ -93,7 +93,7 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
-               if featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+               if featgate.FeatGateRemoteCharts.Enabled() {
                        if err := cli.AddFlag(cmd, &cfg.ChartVersion, 
"chart-version", "", "Choose a remote chart version, otherwise the latest 
version is used", cli.AddFlagOptions{
                                GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
                                Group:                mainFlagGroup,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/cmd/nelm/main.go 
new/nelm-1.4.0/cmd/nelm/main.go
--- old/nelm-1.3.0/cmd/nelm/main.go     2025-05-07 12:46:15.000000000 +0200
+++ new/nelm-1.4.0/cmd/nelm/main.go     2025-05-14 11:22:12.000000000 +0200
@@ -41,12 +41,11 @@
                }
        }
 
-       unsupportedEnvVars := cli.FindUndefinedFlagEnvVarsInEnviron()
-       unsupportedEnvVars = lo.Filter(unsupportedEnvVars, func(env string, _ 
int) bool {
-               return !strings.HasPrefix(env, featgate.FeatGateEnvVarsPrefix)
+       featGatesEnvVars := lo.Map(featgate.FeatGates, func(fg 
*featgate.FeatGate, index int) string {
+               return fg.EnvVarName()
        })
 
-       if len(unsupportedEnvVars) > 0 {
+       if unsupportedEnvVars := 
lo.Without(cli.FindUndefinedFlagEnvVarsInEnviron(), featGatesEnvVars...); 
len(unsupportedEnvVars) > 0 {
                abort(ctx, fmt.Errorf("unsupported environment variable(s): 
%s", strings.Join(unsupportedEnvVars, ",")), 1)
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/cmd/nelm/release_get.go 
new/nelm-1.4.0/cmd/nelm/release_get.go
--- old/nelm-1.3.0/cmd/nelm/release_get.go      2025-05-07 12:46:15.000000000 
+0200
+++ new/nelm-1.4.0/cmd/nelm/release_get.go      2025-05-14 11:22:12.000000000 
+0200
@@ -168,6 +168,13 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
+               if err := cli.AddFlag(cmd, &cfg.PrintValues, "print-values", 
false, "Print Values of the last Helm release", cli.AddFlagOptions{
+                       GetEnvVarRegexesFunc: cli.GetFlagLocalEnvVarRegexes,
+                       Group:                mainFlagGroup,
+               }); err != nil {
+                       return fmt.Errorf("add flag: %w", err)
+               }
+
                if err := cli.AddFlag(cmd, &cfg.ReleaseName, "release", "", 
"The release name. Must be unique within the release namespace", 
cli.AddFlagOptions{
                        GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
                        Group:                mainFlagGroup,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/cmd/nelm/release_install.go 
new/nelm-1.4.0/cmd/nelm/release_install.go
--- old/nelm-1.3.0/cmd/nelm/release_install.go  2025-05-07 12:46:15.000000000 
+0200
+++ new/nelm-1.4.0/cmd/nelm/release_install.go  2025-05-14 11:22:12.000000000 
+0200
@@ -25,7 +25,7 @@
        cfg := &releaseInstallConfig{}
 
        use := "install [options...] -n namespace -r release"
-       if featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+       if featgate.FeatGateRemoteCharts.Enabled() {
                use += " 
[chart-dir|chart-repo-name/chart-name|chart-archive|chart-archive-url]"
        } else {
                use += " [chart-dir]"
@@ -50,7 +50,7 @@
                        })
 
                        if len(args) > 0 {
-                               if 
featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+                               if featgate.FeatGateRemoteCharts.Enabled() {
                                        cfg.Chart = args[0]
                                } else {
                                        cfg.ChartDirPath = args[0]
@@ -100,7 +100,7 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
-               if featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+               if featgate.FeatGateRemoteCharts.Enabled() {
                        if err := cli.AddFlag(cmd, &cfg.ChartVersion, 
"chart-version", "", "Choose a remote chart version, otherwise the latest 
version is used", cli.AddFlagOptions{
                                GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
                                Group:                mainFlagGroup,
@@ -261,6 +261,13 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
+               if err := cli.AddFlag(cmd, &cfg.NoInstallCRDs, 
"no-install-crds", false, `Don't install CRDs from "crds/" directories of 
installed charts`, cli.AddFlagOptions{
+                       GetEnvVarRegexesFunc: cli.GetFlagLocalEnvVarRegexes,
+                       Group:                mainFlagGroup,
+               }); err != nil {
+                       return fmt.Errorf("add flag: %w", err)
+               }
+
                if err := cli.AddFlag(cmd, &cfg.NoProgressTablePrint, 
"no-show-progress", false, "Don't show logs, events and real-time info about 
release resources", cli.AddFlagOptions{
                        GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
                        Group:                progressFlagGroup,
@@ -296,6 +303,13 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
+               if err := cli.AddFlag(cmd, &cfg.ReleaseLabels, 
"release-labels", map[string]string{}, "Add labels to the release. What kind of 
labels depends on the storage driver", cli.AddFlagOptions{
+                       GetEnvVarRegexesFunc: 
cli.GetFlagLocalMultiEnvVarRegexes,
+                       Group:                mainFlagGroup,
+               }); err != nil {
+                       return fmt.Errorf("add flag: %w", err)
+               }
+
                if err := cli.AddFlag(cmd, &cfg.ReleaseName, "release", "", 
"The release name. Must be unique within the release namespace", 
cli.AddFlagOptions{
                        GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
                        Group:                mainFlagGroup,
@@ -364,6 +378,13 @@
                }); err != nil {
                        return fmt.Errorf("add flag: %w", err)
                }
+
+               if err := cli.AddFlag(cmd, &cfg.Timeout, "timeout", 0, "Fail if 
not finished in time", cli.AddFlagOptions{
+                       GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
+                       Group:                mainFlagGroup,
+               }); err != nil {
+                       return fmt.Errorf("add flag: %w", err)
+               }
 
                if err := cli.AddFlag(cmd, &cfg.TrackCreationTimeout, 
"resource-creation-timeout", 0, "Fail if resource creation tracking did not 
finish in time", cli.AddFlagOptions{
                        GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/cmd/nelm/release_plan_install.go 
new/nelm-1.4.0/cmd/nelm/release_plan_install.go
--- old/nelm-1.3.0/cmd/nelm/release_plan_install.go     2025-05-07 
12:46:15.000000000 +0200
+++ new/nelm-1.4.0/cmd/nelm/release_plan_install.go     2025-05-14 
11:22:12.000000000 +0200
@@ -25,7 +25,7 @@
        cfg := &releasePlanInstallConfig{}
 
        use := "install [options...] -n namespace -r release"
-       if featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+       if featgate.FeatGateRemoteCharts.Enabled() {
                use += " 
[chart-dir|chart-repo-name/chart-name|chart-archive|chart-archive-url]"
        } else {
                use += " [chart-dir]"
@@ -50,7 +50,7 @@
                        })
 
                        if len(args) > 0 {
-                               if 
featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+                               if featgate.FeatGateRemoteCharts.Enabled() {
                                        cfg.Chart = args[0]
                                } else {
                                        cfg.ChartDirPath = args[0]
@@ -94,7 +94,7 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
-               if featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) {
+               if featgate.FeatGateRemoteCharts.Enabled() {
                        if err := cli.AddFlag(cmd, &cfg.ChartVersion, 
"chart-version", "", "Choose a remote chart version, otherwise the latest 
version is used", cli.AddFlagOptions{
                                GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
                                Group:                mainFlagGroup,
@@ -247,6 +247,13 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
+               if err := cli.AddFlag(cmd, &cfg.NoInstallCRDs, 
"no-install-crds", false, `Don't install CRDs from "crds/" directories of 
installed charts`, cli.AddFlagOptions{
+                       GetEnvVarRegexesFunc: cli.GetFlagLocalEnvVarRegexes,
+                       Group:                mainFlagGroup,
+               }); err != nil {
+                       return fmt.Errorf("add flag: %w", err)
+               }
+
                if err := cli.AddFlag(cmd, &cfg.RegistryCredentialsPath, 
"oci-chart-repos-creds", action.DefaultRegistryCredentialsPath, "Credentials to 
access OCI chart repositories", cli.AddFlagOptions{
                        GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
                        Group:                chartRepoFlagGroup,
@@ -309,6 +316,13 @@
                }); err != nil {
                        return fmt.Errorf("add flag: %w", err)
                }
+
+               if err := cli.AddFlag(cmd, &cfg.Timeout, "timeout", 0, "Fail if 
not finished in time", cli.AddFlagOptions{
+                       GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
+                       Group:                mainFlagGroup,
+               }); err != nil {
+                       return fmt.Errorf("add flag: %w", err)
+               }
 
                if err := cli.AddFlag(cmd, &cfg.ValuesFileSets, "set-file", 
[]string{}, "Set new values, where the key is the value path and the value is 
the path to the file with the value content", cli.AddFlagOptions{
                        GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/cmd/nelm/release_rollback.go 
new/nelm-1.4.0/cmd/nelm/release_rollback.go
--- old/nelm-1.3.0/cmd/nelm/release_rollback.go 2025-05-07 12:46:15.000000000 
+0200
+++ new/nelm-1.4.0/cmd/nelm/release_rollback.go 2025-05-14 11:22:12.000000000 
+0200
@@ -242,6 +242,13 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
+               if err := cli.AddFlag(cmd, &cfg.Timeout, "timeout", 0, "Fail if 
not finished in time", cli.AddFlagOptions{
+                       GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
+                       Group:                mainFlagGroup,
+               }); err != nil {
+                       return fmt.Errorf("add flag: %w", err)
+               }
+
                if err := cli.AddFlag(cmd, &cfg.TrackCreationTimeout, 
"resource-creation-timeout", 0, "Fail if resource creation tracking did not 
finish in time", cli.AddFlagOptions{
                        GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
                        Group:                progressFlagGroup,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/cmd/nelm/release_uninstall.go 
new/nelm-1.4.0/cmd/nelm/release_uninstall.go
--- old/nelm-1.3.0/cmd/nelm/release_uninstall.go        2025-05-07 
12:46:15.000000000 +0200
+++ new/nelm-1.4.0/cmd/nelm/release_uninstall.go        2025-05-14 
11:22:12.000000000 +0200
@@ -208,6 +208,13 @@
                        return fmt.Errorf("add flag: %w", err)
                }
 
+               if err := cli.AddFlag(cmd, &cfg.Timeout, "timeout", 0, "Fail if 
not finished in time", cli.AddFlagOptions{
+                       GetEnvVarRegexesFunc: 
cli.GetFlagGlobalAndLocalEnvVarRegexes,
+                       Group:                mainFlagGroup,
+               }); err != nil {
+                       return fmt.Errorf("add flag: %w", err)
+               }
+
                return nil
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/cmd/nelm/usage.go 
new/nelm-1.4.0/cmd/nelm/usage.go
--- old/nelm-1.3.0/cmd/nelm/usage.go    2025-05-07 12:46:15.000000000 +0200
+++ new/nelm-1.4.0/cmd/nelm/usage.go    2025-05-14 11:22:12.000000000 +0200
@@ -19,6 +19,13 @@
        "github.com/werf/logboek"
        "github.com/werf/logboek/pkg/types"
        "github.com/werf/nelm/internal/common"
+       "github.com/werf/nelm/pkg/featgate"
+)
+
+const (
+       flagsHelpIndent     = 10
+       featGatesHelpIndent = 10
+       minUsageWrapWidth   = 40
 )
 
 const helpTemplate = `
@@ -60,6 +67,10 @@
 {{- if .HasAvailableLocalFlags}}
 {{ flagsUsage .LocalFlags | trimTrailingWhitespaces }}
 {{- end }}
+
+{{- if not .HasAvailableSubCommands}}
+{{ featGatesUsage | trimTrailingWhitespaces }}
+{{- end }}
 `
 
 var templateFuncs = template.FuncMap{
@@ -67,8 +78,9 @@
        "trimTrailingWhitespaces": func(s string) string {
                return strings.TrimRightFunc(s, unicode.IsSpace)
        },
-       "flagsUsage":    flagsUsage,
-       "commandsUsage": commandsUsage,
+       "flagsUsage":     flagsUsage,
+       "commandsUsage":  commandsUsage,
+       "featGatesUsage": featGatesUsage,
 }
 
 func usageFunc(c *cobra.Command) error {
@@ -89,9 +101,6 @@
 }
 
 func flagsUsage(fset *pflag.FlagSet) string {
-       const helpIndent = 10
-       const minHelpWidthToWrap = 40
-
        terminalWidth := logboek.Streams().Width()
        groupsByPriority, groupedFlags := groupFlags(fset)
 
@@ -126,16 +135,14 @@
                                header += fmt.Sprintf("=%s", flag.DefValue)
                        }
 
-                       helpWrapWidth := terminalWidth - helpIndent
-
                        var help string
-                       if helpWrapWidth > minHelpWidthToWrap {
+                       if terminalWidth > minUsageWrapWidth {
                                help = logboek.FitText(flag.Usage, 
types.FitTextOptions{
-                                       ExtraIndentWidth: helpIndent,
-                                       Width:            helpWrapWidth + 
helpIndent,
+                                       ExtraIndentWidth: flagsHelpIndent,
+                                       Width:            terminalWidth,
                                })
                        } else {
-                               help = fmt.Sprintf("%s%s", strings.Repeat(" ", 
helpIndent), flag.Usage)
+                               help = fmt.Sprintf("%s%s", strings.Repeat(" ", 
flagsHelpIndent), flag.Usage)
                        }
 
                        line := fmt.Sprintf("%s\n%s", header, help)
@@ -282,3 +289,37 @@
        priority    int
        short       string
 }
+
+func featGatesUsage() string {
+       terminalWidth := logboek.Streams().Width()
+
+       buf := new(bytes.Buffer)
+       lines := []string{}
+
+       for i, featGate := range featgate.FeatGates {
+               if i == 0 {
+                       lines = append(lines, "\nFeature gates:\n")
+               }
+
+               header := fmt.Sprintf("      $%s=%v", featGate.EnvVarName(), 
featGate.Default())
+
+               var help string
+               if terminalWidth > minUsageWrapWidth {
+                       help = logboek.FitText(featGate.Help, 
types.FitTextOptions{
+                               ExtraIndentWidth: featGatesHelpIndent,
+                               Width:            terminalWidth,
+                       })
+               } else {
+                       help = fmt.Sprintf("%s%s", strings.Repeat(" ", 
featGatesHelpIndent), featGate.Help)
+               }
+
+               line := fmt.Sprintf("%s\n%s", header, help)
+               lines = append(lines, line)
+       }
+
+       for _, line := range lines {
+               fmt.Fprintln(buf, line)
+       }
+
+       return buf.String()
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/go.mod new/nelm-1.4.0/go.mod
--- old/nelm-1.3.0/go.mod       2025-05-07 12:46:15.000000000 +0200
+++ new/nelm-1.4.0/go.mod       2025-05-14 11:22:12.000000000 +0200
@@ -34,7 +34,7 @@
        github.com/spf13/pflag v1.0.5
        github.com/tidwall/sjson v1.2.5
        github.com/wI2L/jsondiff v0.5.0
-       github.com/werf/3p-helm v0.0.0-20250423070931-cbf97ffb83ad
+       github.com/werf/3p-helm v0.0.0-20250513175502-6861d09b2363
        github.com/werf/common-go v0.0.0-20250417171011-97dbede6f27c
        github.com/werf/kubedog v0.13.1-0.20250411133038-3d8084fab0ec
        github.com/werf/lockgate v0.1.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/go.sum new/nelm-1.4.0/go.sum
--- old/nelm-1.3.0/go.sum       2025-05-07 12:46:15.000000000 +0200
+++ new/nelm-1.4.0/go.sum       2025-05-14 11:22:12.000000000 +0200
@@ -413,8 +413,8 @@
 github.com/tidwall/sjson v1.2.5/go.mod 
h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
 github.com/wI2L/jsondiff v0.5.0 h1:RRMTi/mH+R2aXcPe1VYyvGINJqQfC3R+KSEakuU1Ikw=
 github.com/wI2L/jsondiff v0.5.0/go.mod 
h1:qqG6hnK0Lsrz2BpIVCxWiK9ItsBCpIZQiv0izJjOZ9s=
-github.com/werf/3p-helm v0.0.0-20250423070931-cbf97ffb83ad 
h1:UNOFSDUUCBNX0HSQPzVPySs7BP7KoqofdgszTOFIrrA=
-github.com/werf/3p-helm v0.0.0-20250423070931-cbf97ffb83ad/go.mod 
h1:bwpkc66otpnI2/K8fteIF/IkrHrq6jrAFW5ETHPNa00=
+github.com/werf/3p-helm v0.0.0-20250513175502-6861d09b2363 
h1:71f/6hHWMGpm4+nKCKGbpkVMOd2ePzY//qgSBkEQXsY=
+github.com/werf/3p-helm v0.0.0-20250513175502-6861d09b2363/go.mod 
h1:bwpkc66otpnI2/K8fteIF/IkrHrq6jrAFW5ETHPNa00=
 github.com/werf/common-go v0.0.0-20250417171011-97dbede6f27c 
h1:DOXbzVhCnkn9znHNgiD1UahLu59Dv48iFO/L8i4Z/bI=
 github.com/werf/common-go v0.0.0-20250417171011-97dbede6f27c/go.mod 
h1:taKDUxKmGfqNOlVx1O0ad5vdV4duKexTLO7Rch9HfeA=
 github.com/werf/kubedog v0.13.1-0.20250411133038-3d8084fab0ec 
h1:tyfkagRcJVtfBF4aoxmnE6/idE7YIPo4RqdJQXNoJRg=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/internal/chart/chart_tree.go 
new/nelm-1.4.0/internal/chart/chart_tree.go
--- old/nelm-1.3.0/internal/chart/chart_tree.go 2025-05-07 12:46:15.000000000 
+0200
+++ new/nelm-1.4.0/internal/chart/chart_tree.go 2025-05-14 11:22:12.000000000 
+0200
@@ -37,7 +37,7 @@
 )
 
 func NewChartTree(ctx context.Context, chartPath, releaseName, 
releaseNamespace string, revision int, deployType common.DeployType, opts 
ChartTreeOptions) (*ChartTree, error) {
-       if featgate.FeatGateEnabled(featgate.FeatGateRemoteCharts) && 
!IsLocalChart(chartPath) {
+       if featgate.FeatGateRemoteCharts.Enabled() && !IsLocalChart(chartPath) {
                chartDownloader, chartRef, err := NewChartDownloader(ctx, 
chartPath, opts.RegistryClient, ChartDownloaderOptions{
                        CaFile:        opts.KubeCAPath,
                        SkipTLSVerify: opts.ChartRepoSkipTLSVerify,
@@ -162,16 +162,19 @@
        log.Default.Debug(ctx, "Rendering resources for chart at %q", chartPath)
 
        var standaloneCRDs []*resource.StandaloneCRD
-       for _, crd := range legacyChart.CRDObjects() {
-               for _, manifest := range 
releaseutil.SplitManifests(string(crd.File.Data)) {
-                       if res, err := 
resource.NewStandaloneCRDFromManifest(manifest, 
resource.StandaloneCRDFromManifestOptions{
-                               FilePath:         crd.Filename,
-                               DefaultNamespace: releaseNamespace,
-                               Mapper:           opts.Mapper,
-                       }); err != nil {
-                               return nil, fmt.Errorf("error constructing 
standalone CRD for chart at %q: %w", chartPath, err)
-                       } else {
-                               standaloneCRDs = append(standaloneCRDs, res)
+
+       if !opts.NoStandaloneCRDs {
+               for _, crd := range legacyChart.CRDObjects() {
+                       for _, manifest := range 
releaseutil.SplitManifests(string(crd.File.Data)) {
+                               if res, err := 
resource.NewStandaloneCRDFromManifest(manifest, 
resource.StandaloneCRDFromManifestOptions{
+                                       FilePath:         crd.Filename,
+                                       DefaultNamespace: releaseNamespace,
+                                       Mapper:           opts.Mapper,
+                               }); err != nil {
+                                       return nil, fmt.Errorf("error 
constructing standalone CRD for chart at %q: %w", chartPath, err)
+                               } else {
+                                       standaloneCRDs = append(standaloneCRDs, 
res)
+                               }
                        }
                }
        }
@@ -270,6 +273,7 @@
        KubeConfig             *kube.KubeConfig
        KubeVersion            *chartutil.KubeVersion
        Mapper                 meta.ResettableRESTMapper
+       NoStandaloneCRDs       bool
        RegistryClient         *registry.Client
        SetValues              []string
        StringSetValues        []string
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/internal/release/legacy_release.go 
new/nelm-1.4.0/internal/release/legacy_release.go
--- old/nelm-1.3.0/internal/release/legacy_release.go   2025-05-07 
12:46:15.000000000 +0200
+++ new/nelm-1.4.0/internal/release/legacy_release.go   2025-05-14 
11:22:12.000000000 +0200
@@ -48,6 +48,7 @@
                Manifest: strings.Join(generalResourcesManifests, "\n---\n"),
                Config:   rel.Values(),
                Chart:    rel.LegacyChart(),
+               Labels:   rel.Labels(),
        }
 
        return legacyRel, nil
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/internal/release/release.go 
new/nelm-1.4.0/internal/release/release.go
--- old/nelm-1.3.0/internal/release/release.go  2025-05-07 12:46:15.000000000 
+0200
+++ new/nelm-1.4.0/internal/release/release.go  2025-05-14 11:22:12.000000000 
+0200
@@ -44,32 +44,38 @@
                opts.InfoAnnotations = map[string]string{}
        }
 
+       if opts.Labels == nil {
+               opts.Labels = map[string]string{}
+       }
+
        return &Release{
-               name:             name,
-               namespace:        namespace,
-               revision:         revision,
-               values:           values,
-               legacyChart:      legacyChart,
-               mapper:           opts.Mapper,
-               status:           status,
-               firstDeployed:    opts.FirstDeployed,
-               lastDeployed:     opts.LastDeployed,
                appVersion:       legacyChart.Metadata.AppVersion,
                chartName:        legacyChart.Metadata.Name,
                chartVersion:     legacyChart.Metadata.Version,
-               infoAnnotations:  opts.InfoAnnotations,
-               hookResources:    hookResources,
+               firstDeployed:    opts.FirstDeployed,
                generalResources: generalResources,
+               hookResources:    hookResources,
+               infoAnnotations:  opts.InfoAnnotations,
+               labels:           opts.Labels,
+               lastDeployed:     opts.LastDeployed,
+               legacyChart:      legacyChart,
+               mapper:           opts.Mapper,
+               name:             name,
+               namespace:        namespace,
                notes:            notes,
+               revision:         revision,
+               status:           status,
+               values:           values,
        }, nil
 }
 
 type ReleaseOptions struct {
-       InfoAnnotations map[string]string
-       Status          helmrelease.Status
        FirstDeployed   time.Time
+       InfoAnnotations map[string]string
+       Labels          map[string]string
        LastDeployed    time.Time
        Mapper          meta.ResettableRESTMapper
+       Status          helmrelease.Status
 }
 
 func NewReleaseFromLegacyRelease(legacyRelease *helmrelease.Release, opts 
ReleaseFromLegacyReleaseOptions) (*Release, error) {
@@ -100,12 +106,18 @@
                }
        }
 
-       rel, err := NewRelease(legacyRelease.Name, legacyRelease.Namespace, 
legacyRelease.Version, legacyRelease.Config, legacyRelease.Chart, 
hookResources, generalResources, legacyRelease.Info.Notes, ReleaseOptions{
-               InfoAnnotations: legacyRelease.Info.Annotations,
-               Status:          legacyRelease.Info.Status,
+       vals, err := chartutil.CoalesceValues(legacyRelease.Chart, 
legacyRelease.Config)
+       if err != nil {
+               return nil, fmt.Errorf("coalesce values for legacy release %q 
(namespace: %q, revision: %d): %w", legacyRelease.Name, 
legacyRelease.Namespace, legacyRelease.Version, err)
+       }
+
+       rel, err := NewRelease(legacyRelease.Name, legacyRelease.Namespace, 
legacyRelease.Version, vals, legacyRelease.Chart, hookResources, 
generalResources, legacyRelease.Info.Notes, ReleaseOptions{
                FirstDeployed:   legacyRelease.Info.FirstDeployed.Time,
+               InfoAnnotations: legacyRelease.Info.Annotations,
+               Labels:          legacyRelease.Labels,
                LastDeployed:    legacyRelease.Info.LastDeployed.Time,
                Mapper:          opts.Mapper,
+               Status:          legacyRelease.Info.Status,
        })
        if err != nil {
                return nil, fmt.Errorf("error building release %q (namespace: 
%q, revision: %d): %w", legacyRelease.Name, legacyRelease.Namespace, 
legacyRelease.Version, err)
@@ -120,24 +132,23 @@
 }
 
 type Release struct {
-       name        string
-       namespace   string
-       revision    int
-       values      map[string]interface{}
-       legacyChart *chart.Chart
-       mapper      meta.ResettableRESTMapper
-
-       status          helmrelease.Status
-       firstDeployed   time.Time
-       lastDeployed    time.Time
-       appVersion      string
-       chartName       string
-       chartVersion    string
-       infoAnnotations map[string]string
-
-       hookResources    []*resource.HookResource
+       appVersion       string
+       chartName        string
+       chartVersion     string
+       firstDeployed    time.Time
        generalResources []*resource.GeneralResource
+       hookResources    []*resource.HookResource
+       infoAnnotations  map[string]string
+       labels           map[string]string
+       lastDeployed     time.Time
+       legacyChart      *chart.Chart
+       mapper           meta.ResettableRESTMapper
+       name             string
+       namespace        string
        notes            string
+       revision         int
+       status           helmrelease.Status
+       values           map[string]interface{}
 }
 
 func (r *Release) Name() string {
@@ -200,6 +211,10 @@
        return r.infoAnnotations
 }
 
+func (r *Release) Labels() map[string]string {
+       return r.labels
+}
+
 func (r *Release) ID() string {
        return fmt.Sprintf("%s:%s:%d", r.namespace, r.name, r.revision)
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/pkg/action/chart_render.go 
new/nelm-1.4.0/pkg/action/chart_render.go
--- old/nelm-1.3.0/pkg/action/chart_render.go   2025-05-07 12:46:15.000000000 
+0200
+++ new/nelm-1.4.0/pkg/action/chart_render.go   2025-05-14 11:22:12.000000000 
+0200
@@ -66,6 +66,7 @@
        LogRegistryStreamOut         io.Writer
        NetworkParallelism           int
        OutputFilePath               string
+       OutputNoPrint                bool
        RegistryCredentialsPath      string
        ReleaseName                  string
        ReleaseNamespace             string
@@ -84,23 +85,23 @@
        ValuesStringSets             []string
 }
 
-func ChartRender(ctx context.Context, opts ChartRenderOptions) error {
+func ChartRender(ctx context.Context, opts ChartRenderOptions) 
(*ChartRenderResultV1, error) {
        actionLock.Lock()
        defer actionLock.Unlock()
 
        currentDir, err := os.Getwd()
        if err != nil {
-               return fmt.Errorf("get current working directory: %w", err)
+               return nil, fmt.Errorf("get current working directory: %w", err)
        }
 
        homeDir, err := os.UserHomeDir()
        if err != nil {
-               return fmt.Errorf("get home directory: %w", err)
+               return nil, fmt.Errorf("get home directory: %w", err)
        }
 
        opts, err = applyChartRenderOptionsDefaults(opts, currentDir, homeDir)
        if err != nil {
-               return fmt.Errorf("build chart render options: %w", err)
+               return nil, fmt.Errorf("build chart render options: %w", err)
        }
 
        if opts.SecretKey != "" {
@@ -136,12 +137,12 @@
                        Token:                 opts.KubeToken,
                })
                if err != nil {
-                       return fmt.Errorf("construct kube config: %w", err)
+                       return nil, fmt.Errorf("construct kube config: %w", err)
                }
 
                clientFactory, err = kube.NewClientFactory(ctx, kubeConfig)
                if err != nil {
-                       return fmt.Errorf("construct kube client factory: %w", 
err)
+                       return nil, fmt.Errorf("construct kube client factory: 
%w", err)
                }
        }
 
@@ -160,7 +161,7 @@
 
        helmRegistryClient, err := registry.NewClient(helmRegistryClientOpts...)
        if err != nil {
-               return fmt.Errorf("construct registry client: %w", err)
+               return nil, fmt.Errorf("construct registry client: %w", err)
        }
 
        releaseStorageOptions := release.ReleaseStorageOptions{
@@ -173,7 +174,7 @@
 
        releaseStorage, err := release.NewReleaseStorage(ctx, 
opts.ReleaseNamespace, opts.ReleaseStorageDriver, releaseStorageOptions)
        if err != nil {
-               return fmt.Errorf("construct release storage: %w", err)
+               return nil, fmt.Errorf("construct release storage: %w", err)
        }
 
        chartextender.DefaultChartAPIVersion = opts.DefaultChartAPIVersion
@@ -200,17 +201,17 @@
                historyOptions,
        )
        if err != nil {
-               return fmt.Errorf("construct release history: %w", err)
+               return nil, fmt.Errorf("construct release history: %w", err)
        }
 
        prevRelease, prevReleaseFound, err := history.LastRelease()
        if err != nil {
-               return fmt.Errorf("get last release: %w", err)
+               return nil, fmt.Errorf("get last release: %w", err)
        }
 
        _, prevDeployedReleaseFound, err := history.LastDeployedRelease()
        if err != nil {
-               return fmt.Errorf("get last deployed release: %w", err)
+               return nil, fmt.Errorf("get last deployed release: %w", err)
        }
 
        var newRevision int
@@ -248,7 +249,7 @@
        } else {
                ver, err := chartutil.ParseKubeVersion(opts.LocalKubeVersion)
                if err != nil {
-                       return fmt.Errorf("parse local kube version %q: %w", 
opts.LocalKubeVersion, err)
+                       return nil, fmt.Errorf("parse local kube version %q: 
%w", opts.LocalKubeVersion, err)
                }
 
                chartTreeOptions.KubeVersion = ver
@@ -264,7 +265,7 @@
                chartTreeOptions,
        )
        if err != nil {
-               return fmt.Errorf("construct chart tree: %w", err)
+               return nil, fmt.Errorf("construct chart tree: %w", err)
        }
 
        var prevRelGeneralResources []*resource.GeneralResource
@@ -315,20 +316,20 @@
        )
 
        if err := resProcessor.Process(ctx); err != nil {
-               return fmt.Errorf("process resources: %w", err)
+               return nil, fmt.Errorf("process resources: %w", err)
        }
 
        var showFiles []string
        for _, file := range opts.ShowOnlyFiles {
                absFile, err := filepath.Abs(file)
                if err != nil {
-                       return fmt.Errorf("get absolute path for %q: %w", file, 
err)
+                       return nil, fmt.Errorf("get absolute path for %q: %w", 
file, err)
                }
 
                if strings.HasPrefix(absFile, opts.Chart) {
                        f, err := filepath.Rel(opts.Chart, absFile)
                        if err != nil {
-                               return fmt.Errorf("get relative path for %q: 
%w", absFile, err)
+                               return nil, fmt.Errorf("get relative path for 
%q: %w", absFile, err)
                        }
 
                        if !strings.HasPrefix(f, chartTree.Name()) {
@@ -352,7 +353,7 @@
        if opts.OutputFilePath != "" {
                file, err := os.Create(opts.OutputFilePath)
                if err != nil {
-                       return fmt.Errorf("create chart render output file %q: 
%w", opts.OutputFilePath, err)
+                       return nil, fmt.Errorf("create chart render output file 
%q: %w", opts.OutputFilePath, err)
                }
                defer file.Close()
 
@@ -365,16 +366,22 @@
                }
        }
 
-       if opts.ShowCRDs {
-               for _, resource := range 
resProcessor.DeployableStandaloneCRDs() {
-                       if len(showFiles) > 0 && !lo.Contains(showFiles, 
resource.FilePath()) {
-                               continue
-                       }
+       result := &ChartRenderResultV1{
+               APIVersion: ChartRenderResultApiVersionV1,
+       }
 
+       for _, resource := range resProcessor.DeployableStandaloneCRDs() {
+               if len(showFiles) > 0 && !lo.Contains(showFiles, 
resource.FilePath()) {
+                       continue
+               }
+
+               if opts.ShowCRDs {
                        if err := renderResource(resource.Unstructured(), 
resource.FilePath(), renderOutStream, renderColorLevel); err != nil {
-                               return fmt.Errorf("render CRD %q: %w", 
resource.HumanID(), err)
+                               return nil, fmt.Errorf("render CRD %q: %w", 
resource.HumanID(), err)
                        }
                }
+
+               result.CRDs = append(result.CRDs, 
resource.Unstructured().Object)
        }
 
        for _, resource := range resProcessor.DeployableHookResources() {
@@ -383,8 +390,10 @@
                }
 
                if err := renderResource(resource.Unstructured(), 
resource.FilePath(), renderOutStream, renderColorLevel); err != nil {
-                       return fmt.Errorf("render hook resource %q: %w", 
resource.HumanID(), err)
+                       return nil, fmt.Errorf("render hook resource %q: %w", 
resource.HumanID(), err)
                }
+
+               result.Hooks = append(result.Hooks, 
resource.Unstructured().Object)
        }
 
        for _, resource := range resProcessor.DeployableGeneralResources() {
@@ -393,11 +402,13 @@
                }
 
                if err := renderResource(resource.Unstructured(), 
resource.FilePath(), renderOutStream, renderColorLevel); err != nil {
-                       return fmt.Errorf("render general resource %q: %w", 
resource.HumanID(), err)
+                       return nil, fmt.Errorf("render general resource %q: 
%w", resource.HumanID(), err)
                }
+
+               result.Resources = append(result.Resources, 
resource.Unstructured().Object)
        }
 
-       return nil
+       return result, nil
 }
 
 func applyChartRenderOptionsDefaults(opts ChartRenderOptions, currentDir, 
homeDir string) (ChartRenderOptions, error) {
@@ -490,3 +501,12 @@
 
        return nil
 }
+
+const ChartRenderResultApiVersionV1 = "v1"
+
+type ChartRenderResultV1 struct {
+       APIVersion string                   `json:"apiVersion"`
+       CRDs       []map[string]interface{} `json:"crds"`
+       Hooks      []map[string]interface{} `json:"hooks"`
+       Resources  []map[string]interface{} `json:"resources"`
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/pkg/action/errors.go 
new/nelm-1.4.0/pkg/action/errors.go
--- old/nelm-1.3.0/pkg/action/errors.go 1970-01-01 01:00:00.000000000 +0100
+++ new/nelm-1.4.0/pkg/action/errors.go 2025-05-14 11:22:12.000000000 +0200
@@ -0,0 +1,22 @@
+package action
+
+import "fmt"
+
+type ReleaseNotFoundError struct {
+       ReleaseName      string
+       ReleaseNamespace string
+}
+
+func (e *ReleaseNotFoundError) Error() string {
+       return fmt.Sprintf("release %q (namespace %q) not found", 
e.ReleaseName, e.ReleaseNamespace)
+}
+
+type ReleaseRevisionNotFoundError struct {
+       ReleaseName      string
+       ReleaseNamespace string
+       Revision         int
+}
+
+func (e *ReleaseRevisionNotFoundError) Error() string {
+       return fmt.Sprintf("revision %d of release %q (namespace %q) not 
found", e.Revision, e.ReleaseName, e.ReleaseNamespace)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/pkg/action/release_get.go 
new/nelm-1.4.0/pkg/action/release_get.go
--- old/nelm-1.3.0/pkg/action/release_get.go    2025-05-07 12:46:15.000000000 
+0200
+++ new/nelm-1.4.0/pkg/action/release_get.go    2025-05-14 11:22:12.000000000 
+0200
@@ -39,6 +39,7 @@
        NetworkParallelism   int
        OutputFormat         string
        OutputNoPrint        bool
+       PrintValues          bool
        ReleaseStorageDriver string
        Revision             int
        SQLConnectionString  string
@@ -119,27 +120,30 @@
                return nil, fmt.Errorf("construct release history: %w", err)
        }
 
-       var (
-               release      *release.Release
-               releaseFound bool
-       )
+       if history.Empty() {
+               return nil, &ReleaseNotFoundError{
+                       ReleaseName:      releaseName,
+                       ReleaseNamespace: releaseNamespace,
+               }
+       }
+
+       var release *release.Release
        if opts.Revision == 0 {
-               release, releaseFound, err = history.LastRelease()
+               release, _, err = history.LastRelease()
                if err != nil {
                        return nil, fmt.Errorf("get last release: %w", err)
                }
        } else {
-               release, releaseFound, err = history.Release(opts.Revision)
+               var revisionFound bool
+               release, revisionFound, err = history.Release(opts.Revision)
                if err != nil {
                        return nil, fmt.Errorf("get release revision %d: %w", 
opts.Revision, err)
-               }
-       }
-
-       if !releaseFound {
-               if opts.Revision == 0 {
-                       return nil, fmt.Errorf("release %q (namespace %q) not 
found", releaseName, releaseNamespace)
-               } else {
-                       return nil, fmt.Errorf("revision %d of release %q 
(namespace %q) not found", opts.Revision, releaseName, releaseNamespace)
+               } else if !revisionFound {
+                       return nil, &ReleaseRevisionNotFoundError{
+                               ReleaseName:      releaseName,
+                               ReleaseNamespace: releaseNamespace,
+                               Revision:         opts.Revision,
+                       }
                }
        }
 
@@ -161,7 +165,8 @@
                        Version:    release.ChartVersion(),
                        AppVersion: release.AppVersion(),
                },
-               Notes: release.Notes(),
+               Notes:  release.Notes(),
+               Values: release.Values(),
        }
 
        for _, hook := range release.HookResources() {
@@ -175,6 +180,11 @@
        if !opts.OutputNoPrint {
                var resultMessage string
 
+               savedValues := result.Values
+               if !opts.PrintValues {
+                       result.Values = nil
+               }
+
                switch opts.OutputFormat {
                case JsonOutputFormat:
                        b, err := json.MarshalIndent(result, "", 
strings.Repeat(" ", 2))
@@ -194,6 +204,10 @@
                        return nil, fmt.Errorf("unknown output format %q", 
opts.OutputFormat)
                }
 
+               if !opts.PrintValues {
+                       result.Values = savedValues
+               }
+
                var colorLevel color.Level
                if color.Enable {
                        colorLevel = color.TermColorLevel()
@@ -250,6 +264,7 @@
        Release    *ReleaseGetResultRelease `json:"release"`
        Chart      *ReleaseGetResultChart   `json:"chart"`
        Notes      string                   `json:"notes"`
+       Values     map[string]interface{}   `json:"values"`
        Hooks      []map[string]interface{} `json:"hooks"`
        Resources  []map[string]interface{} `json:"resources"`
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/pkg/action/release_install.go 
new/nelm-1.4.0/pkg/action/release_install.go
--- old/nelm-1.3.0/pkg/action/release_install.go        2025-05-07 
12:46:15.000000000 +0200
+++ new/nelm-1.4.0/pkg/action/release_install.go        2025-05-14 
11:22:12.000000000 +0200
@@ -76,11 +76,13 @@
        KubeToken                    string
        LogRegistryStreamOut         io.Writer
        NetworkParallelism           int
+       NoInstallCRDs                bool
        NoProgressTablePrint         bool
        ProgressTablePrintInterval   time.Duration
        RegistryCredentialsPath      string
        ReleaseHistoryLimit          int
        ReleaseInfoAnnotations       map[string]string
+       ReleaseLabels                map[string]string
        ReleaseStorageDriver         string
        RollbackGraphPath            string
        SQLConnectionString          string
@@ -90,6 +92,7 @@
        SecretWorkDir                string
        SubNotes                     bool
        TempDirPath                  string
+       Timeout                      time.Duration
        TrackCreationTimeout         time.Duration
        TrackDeletionTimeout         time.Duration
        TrackReadinessTimeout        time.Duration
@@ -103,6 +106,29 @@
        actionLock.Lock()
        defer actionLock.Unlock()
 
+       if opts.Timeout == 0 {
+               return releaseInstall(ctx, releaseName, releaseNamespace, opts)
+       }
+
+       ctx, ctxCancelFn := context.WithTimeoutCause(ctx, opts.Timeout, 
fmt.Errorf("action timed out after %s", opts.Timeout.String()))
+       defer ctxCancelFn()
+
+       actionCh := make(chan error, 1)
+       go func() {
+               actionCh <- releaseInstall(ctx, releaseName, releaseNamespace, 
opts)
+       }()
+
+       for {
+               select {
+               case err := <-actionCh:
+                       return err
+               case <-ctx.Done():
+                       return context.Cause(ctx)
+               }
+       }
+}
+
+func releaseInstall(ctx context.Context, releaseName, releaseNamespace string, 
opts ReleaseInstallOptions) error {
        currentDir, err := os.Getwd()
        if err != nil {
                return fmt.Errorf("get current working directory: %w", err)
@@ -280,6 +306,7 @@
                        KubeCAPath:             opts.KubeCAPath,
                        KubeConfig:             clientFactory.KubeConfig(),
                        Mapper:                 clientFactory.Mapper(),
+                       NoStandaloneCRDs:       opts.NoInstallCRDs,
                        RegistryClient:         helmRegistryClient,
                        SetValues:              opts.ValuesSets,
                        StringSetValues:        opts.ValuesStringSets,
@@ -355,6 +382,7 @@
                        InfoAnnotations: opts.ReleaseInfoAnnotations,
                        FirstDeployed:   firstDeployed,
                        Mapper:          clientFactory.Mapper(),
+                       Labels:          opts.ReleaseLabels,
                },
        )
        if err != nil {
@@ -946,8 +974,10 @@
                resProcessor.ReleasableGeneralResources(),
                prevDeployedRelease.Notes(),
                release.ReleaseOptions{
-                       FirstDeployed: prevDeployedRelease.FirstDeployed(),
-                       Mapper:        clientFactory.Mapper(),
+                       InfoAnnotations: prevDeployedRelease.InfoAnnotations(),
+                       FirstDeployed:   prevDeployedRelease.FirstDeployed(),
+                       Mapper:          clientFactory.Mapper(),
+                       Labels:          prevDeployedRelease.Labels(),
                },
        )
        if err != nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/pkg/action/release_plan_install.go 
new/nelm-1.4.0/pkg/action/release_plan_install.go
--- old/nelm-1.3.0/pkg/action/release_plan_install.go   2025-05-07 
12:46:15.000000000 +0200
+++ new/nelm-1.4.0/pkg/action/release_plan_install.go   2025-05-14 
11:22:12.000000000 +0200
@@ -65,6 +65,7 @@
        KubeToken                    string
        LogRegistryStreamOut         io.Writer
        NetworkParallelism           int
+       NoInstallCRDs                bool
        RegistryCredentialsPath      string
        ReleaseStorageDriver         string
        SQLConnectionString          string
@@ -73,6 +74,7 @@
        SecretValuesPaths            []string
        SecretWorkDir                string
        TempDirPath                  string
+       Timeout                      time.Duration
        ValuesFileSets               []string
        ValuesFilesPaths             []string
        ValuesSets                   []string
@@ -83,6 +85,29 @@
        actionLock.Lock()
        defer actionLock.Unlock()
 
+       if opts.Timeout == 0 {
+               return releasePlanInstall(ctx, releaseName, releaseNamespace, 
opts)
+       }
+
+       ctx, ctxCancelFn := context.WithTimeoutCause(ctx, opts.Timeout, 
fmt.Errorf("action timed out after %s", opts.Timeout.String()))
+       defer ctxCancelFn()
+
+       actionCh := make(chan error, 1)
+       go func() {
+               actionCh <- releasePlanInstall(ctx, releaseName, 
releaseNamespace, opts)
+       }()
+
+       for {
+               select {
+               case err := <-actionCh:
+                       return err
+               case <-ctx.Done():
+                       return context.Cause(ctx)
+               }
+       }
+}
+
+func releasePlanInstall(ctx context.Context, releaseName, releaseNamespace 
string, opts ReleasePlanInstallOptions) error {
        currentDir, err := os.Getwd()
        if err != nil {
                return fmt.Errorf("get current working directory: %w", err)
@@ -237,6 +262,7 @@
                        KubeCAPath:             opts.KubeCAPath,
                        KubeConfig:             clientFactory.KubeConfig(),
                        Mapper:                 clientFactory.Mapper(),
+                       NoStandaloneCRDs:       opts.NoInstallCRDs,
                        RegistryClient:         helmRegistryClient,
                        SetValues:              opts.ValuesSets,
                        StringSetValues:        opts.ValuesStringSets,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/pkg/action/release_rollback.go 
new/nelm-1.4.0/pkg/action/release_rollback.go
--- old/nelm-1.3.0/pkg/action/release_rollback.go       2025-05-07 
12:46:15.000000000 +0200
+++ new/nelm-1.4.0/pkg/action/release_rollback.go       2025-05-14 
11:22:12.000000000 +0200
@@ -54,6 +54,7 @@
        RollbackReportPath         string
        SQLConnectionString        string
        TempDirPath                string
+       Timeout                    time.Duration
        TrackCreationTimeout       time.Duration
        TrackDeletionTimeout       time.Duration
        TrackReadinessTimeout      time.Duration
@@ -63,6 +64,29 @@
        actionLock.Lock()
        defer actionLock.Unlock()
 
+       if opts.Timeout == 0 {
+               return releaseRollback(ctx, releaseName, releaseNamespace, opts)
+       }
+
+       ctx, ctxCancelFn := context.WithTimeoutCause(ctx, opts.Timeout, 
fmt.Errorf("action timed out after %s", opts.Timeout.String()))
+       defer ctxCancelFn()
+
+       actionCh := make(chan error, 1)
+       go func() {
+               actionCh <- releaseRollback(ctx, releaseName, releaseNamespace, 
opts)
+       }()
+
+       for {
+               select {
+               case err := <-actionCh:
+                       return err
+               case <-ctx.Done():
+                       return context.Cause(ctx)
+               }
+       }
+}
+
+func releaseRollback(ctx context.Context, releaseName, releaseNamespace 
string, opts ReleaseRollbackOptions) error {
        homeDir, err := os.UserHomeDir()
        if err != nil {
                return fmt.Errorf("get home directory: %w", err)
@@ -241,8 +265,10 @@
                resProcessor.ReleasableGeneralResources(),
                notes,
                release.ReleaseOptions{
-                       FirstDeployed: firstDeployed,
-                       Mapper:        clientFactory.Mapper(),
+                       InfoAnnotations: releaseToRollback.InfoAnnotations(),
+                       FirstDeployed:   firstDeployed,
+                       Mapper:          clientFactory.Mapper(),
+                       Labels:          releaseToRollback.Labels(),
                },
        )
        if err != nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/pkg/action/release_uninstall.go 
new/nelm-1.4.0/pkg/action/release_uninstall.go
--- old/nelm-1.3.0/pkg/action/release_uninstall.go      2025-05-07 
12:46:15.000000000 +0200
+++ new/nelm-1.4.0/pkg/action/release_uninstall.go      2025-05-14 
11:22:12.000000000 +0200
@@ -49,12 +49,36 @@
        ReleaseHistoryLimit        int
        ReleaseStorageDriver       string
        TempDirPath                string
+       Timeout                    time.Duration
 }
 
 func ReleaseUninstall(ctx context.Context, releaseName, releaseNamespace 
string, opts ReleaseUninstallOptions) error {
        actionLock.Lock()
        defer actionLock.Unlock()
 
+       if opts.Timeout == 0 {
+               return releaseUninstall(ctx, releaseName, releaseNamespace, 
opts)
+       }
+
+       ctx, ctxCancelFn := context.WithTimeoutCause(ctx, opts.Timeout, 
fmt.Errorf("action timed out after %s", opts.Timeout.String()))
+       defer ctxCancelFn()
+
+       actionCh := make(chan error, 1)
+       go func() {
+               actionCh <- releaseUninstall(ctx, releaseName, 
releaseNamespace, opts)
+       }()
+
+       for {
+               select {
+               case err := <-actionCh:
+                       return err
+               case <-ctx.Done():
+                       return context.Cause(ctx)
+               }
+       }
+}
+
+func releaseUninstall(ctx context.Context, releaseName, releaseNamespace 
string, opts ReleaseUninstallOptions) error {
        currentDir, err := os.Getwd()
        if err != nil {
                return fmt.Errorf("get current working directory: %w", err)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/pkg/featgate/feat.go 
new/nelm-1.4.0/pkg/featgate/feat.go
--- old/nelm-1.3.0/pkg/featgate/feat.go 2025-05-07 12:46:15.000000000 +0200
+++ new/nelm-1.4.0/pkg/featgate/feat.go 2025-05-14 11:22:12.000000000 +0200
@@ -8,13 +8,41 @@
        "github.com/werf/nelm/internal/common"
 )
 
-const (
+var (
+       FeatGateEnvVarsPrefix = caps.ToScreamingSnake(common.Brand) + "_FEAT_"
+       FeatGates             = []*FeatGate{}
+
        // TODO(v2): always enable
-       FeatGateRemoteCharts = "remote-charts"
+       FeatGateRemoteCharts = NewFeatGate(
+               "remote-charts",
+               `Allow not only local, but also remote charts as an argument to 
cli commands. Also adds the "--chart-version" option`,
+       )
 )
 
-var FeatGateEnvVarsPrefix = caps.ToScreamingSnake(common.Brand) + "_FEAT_"
+func NewFeatGate(name, help string) *FeatGate {
+       fg := &FeatGate{
+               Name: name,
+               Help: help,
+       }
+
+       FeatGates = append(FeatGates, fg)
+
+       return fg
+}
+
+type FeatGate struct {
+       Name string
+       Help string
+}
+
+func (g *FeatGate) EnvVarName() string {
+       return FeatGateEnvVarsPrefix + caps.ToScreamingSnake(g.Name)
+}
+
+func (g *FeatGate) Default() bool {
+       return false
+}
 
-func FeatGateEnabled(featGate string) bool {
-       return os.Getenv(FeatGateEnvVarsPrefix+caps.ToScreamingSnake(featGate)) 
== "true"
+func (g *FeatGate) Enabled() bool {
+       return os.Getenv(g.EnvVarName()) == "true"
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nelm-1.3.0/trdl_channels.yaml 
new/nelm-1.4.0/trdl_channels.yaml
--- old/nelm-1.3.0/trdl_channels.yaml   2025-05-07 12:46:15.000000000 +0200
+++ new/nelm-1.4.0/trdl_channels.yaml   2025-05-14 11:22:12.000000000 +0200
@@ -2,12 +2,12 @@
   - name: "1"
     channels:
       - name: alpha
-        version: 1.2.2
+        version: 1.3.0
       - name: beta
         version: 1.2.2
       - name: ea
-        version: 1.1.5
+        version: 1.2.2
       - name: stable
-        version: 1.1.1
+        version: 1.1.5
       - name: rock-solid
         version: 1.1.1

++++++ nelm.obsinfo ++++++
--- /var/tmp/diff_new_pack.Hz7Vjg/_old  2025-05-14 17:02:35.646722908 +0200
+++ /var/tmp/diff_new_pack.Hz7Vjg/_new  2025-05-14 17:02:35.650723076 +0200
@@ -1,5 +1,5 @@
 name: nelm
-version: 1.3.0
-mtime: 1746614775
-commit: d2d74179b19089543e60d302f74e969f6cbe8d6c
+version: 1.4.0
+mtime: 1747214532
+commit: bbc21f6c6e7bc0b7b7791fd575a3899a17dda2b5
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/nelm/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.nelm.new.30101/vendor.tar.gz differ: char 130, line 
4

Reply via email to